# -*- coding: utf-8 -*-
"""Generic functions to be used for GUI exploration
"""
import inspect
import typing
import os
import textwrap
import numpy as np
import mpmath
import fractalshades as fs
import fractalshades.settings
import fractalshades.colors
import fractalshades.gui
import fractalshades.projection
from fractalshades.postproc import (
Postproc_batch,
Continuous_iter_pp,
Fieldlines_pp,
DEM_pp,
DEM_normal_pp,
Raw_pp,
Attr_pp,
Attr_normal_pp,
Fractal_array,
)
from fractalshades.colors.layers import (
Color_layer,
Bool_layer,
Normal_map_layer,
Grey_layer,
Disp_layer,
Virtual_layer,
Overlay_mode,
Blinn_lighting
)
from fractalshades.utils import Code_writer
usage = f"""# -*- coding: utf-8 -*-
\"\"\"============================================================================
Auto-generated from fractalshades GUI, version {fs.__version__}.
Save to `<file>.py` and run as a python script:
> python <file>.py
============================================================================\"\"\"
"""
usage_movie = f"""# -*- coding: utf-8 -*-
\"\"\"============================================================================
Auto-generated from fractalshades GUI, version {fs.__version__}.
Save to `<file>.py` and use its plotter in the movie making main script
> from <file> import get_plotter, plot_kwargs
============================================================================\"\"\"
"""
script_header = """
import os
import typing
import numpy as np
import mpmath
from PyQt6 import QtGui
import fractalshades
import fractalshades as fs
import fractalshades.models as fsm
import fractalshades.gui as fsgui
import fractalshades.colors as fscolors
import fractalshades.projection
from fractalshades.postproc import (
Postproc_batch,
Continuous_iter_pp,
DEM_normal_pp,
Fieldlines_pp,
DEM_pp,
Raw_pp,
Attr_pp,
Attr_normal_pp,
Fractal_array
)
from fractalshades.colors.layers import (
Color_layer,
Bool_layer,
Normal_map_layer,
Grey_layer,
Disp_layer,
Virtual_layer,
Blinn_lighting,
Overlay_mode
)
# Note: in batch mode, edit this line to change the base directory
plot_dir = os.path.splitext(os.path.realpath(__file__))[0]
# Note: in batch mode, edit this line to change the local projection
# you may also call `plot` with a modified `batch_params` parameters
# (the latter allows to call from another module)
projection = fs.projection.Cartesian()
batch_params = {
"projection": projection
}
"""
script_footer = """
if __name__ == "__main__":
plot(**plot_kwargs, batch_params=batch_params)
"""
def script_title_sep(title, indent=0):
""" Return a script comment line with the given title """
sep_line = " " * 4 * indent + "#" + "-" * (78 - 4 * indent) + "\n"
return (
"\n\n"
+ sep_line
+ " " * 4 * indent + "# " + title + "\n"
+ sep_line
)
def script(source, kwargs, movie=False):
""" Writes source code for this script """
if movie:
full_header = usage_movie + script_header
else:
full_header = usage + script_header
func_params_str = "plot_kwargs = " + script_repr(kwargs, indent=0)
source = (" " * 0) + source.replace("\n", "\n" + " " * 0)
script = (
full_header
+ script_title_sep("Parameters - user editable ", 0)
+ func_params_str
+ script_title_sep("Function - /!\ do not modify this section", 0)
+ source
+ ("" if movie else script_footer)
+ "\n"
)
return script
def script_repr(obj, indent=0):
""" Simple alias for Code_writer.var_tocode :
string source code for an object
"""
shift = " " * (4 * indent)
code = Code_writer.var_tocode(obj)
return shift + code.replace("\n", "\n" + shift)
def script_assignments(kwargs, indent=0):
""" The parameter assignement part of the overall script:
param1 = val1
param2 = val2
...
"""
shift = " " * (4 * indent)
ret = "\n".join(
[(k + " = " + script_repr(v)) for (k, v) in kwargs.items()]
)
ret = shift + ret.replace("\n", "\n" + shift)
return ret
def getsource(callable_, movie=False):
""" Return the source code for this callable
"""
if hasattr(callable_, "getsource"):
return callable_.getsource(movie=movie)
else:
# Default: use inspect - use dedent to get consistent indentation level
if movie:
raise RuntimeError(
"Movie plotter-script not implemented for a GUI based on "
"a standard function, construct the plotter script "
"manually, or switch to a GUItemplate implementation."
)
# Changes the function name to: `plot` (to get consistent call)
# remove the default values - to avoid potential scoping issues
source = textwrap.dedent(inspect.getsource(callable_))
sgn = signature(callable_)
func_name, func_params, func_body = split_source(source, sgn)
str_params = get_str_params(func_params)
return "def plot(" + str_params + func_body
def split_source(func_source, func_signature):
"""
Parameters
----------
func_source: string
Function source code to be parsed
func_signature: Signature
Signature of this function
Returns
-------
(func_name, func_params, func_body)
func_name: str
The extracted string "def func_name"
func_params: mapping
param -> param_txt where param_txt is the text
describing the parameter in the function source code ie:
"param_x: type = xxx" (as delimited by quotes)
func_body: str
the func body including the closing parameters parenthesis
"""
paren_stack = [] # stack for ()
bracket_stack = [] # stack for []
braces_stack = [] # stack for {}
name_ic = 0 # index of the first opening (
func_params = {}
body_ic = 0 # index of the matching )
param_beg = 0 # position of the , or the ( for the forst param
iparam = 0 # next_expected parameter
param_names = list(func_signature.parameters.keys())
n_params = len(param_names)
started = False
for ic, c in enumerate(func_source):
if not started:
if c == "(":
started = True
paren_stack.append(ic)
name_ic = param_beg = ic
continue
# Here we are in the parameters
if c in "([{}])":
if c == ")":
paren_stack.pop()
elif c == "]":
bracket_stack.pop()
elif c == "]":
braces_stack.pop()
elif c == "(":
paren_stack.append(ic)
elif c == "[":
bracket_stack.append(ic)
elif c == "{":
braces_stack.append(ic)
if len(paren_stack) == 0:
body_ic = ic
pname = param_names[iparam]
func_params[pname] = func_source[param_beg + 1: ic]
break # finished reading the parameter block
may_end_param = (
len(paren_stack) == 1
and len(bracket_stack) == 0
and len(braces_stack) == 0
)
if c == "," and may_end_param:
# This is the end of a param declaration
pname = param_names[iparam]
func_params[pname] = func_source[param_beg + 1: ic]
iparam += 1
if iparam == n_params:
# because might have a last comma...
body_ic = ic
break
param_beg = ic
func_name = func_source[:name_ic]
func_body = func_source[body_ic:]
# Drop everything from the body before the ")" - avoid a potential
# ",," if the original params source code ends with ","
incipit = func_body.find(")")
func_body = func_body[incipit:]
return func_name, func_params, func_body
def get_str_params(func_params):
""" Remove the parameters default values from the signature """
str_params = ""
for p_name, v in func_params.items():
decl, val = v.split("=") # dec -> <pname: annotation> / val -> <val>
str_params += decl + ","
return str_params
def signature(callable_):
""" Return the signature for this callable
"""
if hasattr(callable_, "signature"):
return callable_.signature()
else:
return inspect.signature(callable_)
class GUItemplate:
""" Base class for all classes implementing a GUI-template function """
def __init__(self, fractal):
self.tuned_defaults = {}
self.tuned_annotations = {}
self.tuned_annotations_str = {}
self.partial_vals = {}
self.set_default("fractal", fractal)
def set_default(self, pname, value):
""" Change the default for param pname to tuned_annotations"""
self.tuned_defaults[pname] = value
def set_annotation(self, pname, tuned_annotations, annot_str):
""" Change the annotation for param pname to tuned_annotations
Parameters:
-----------
pname: str
The name of the parameter to be modified
tuned_annotations: annotation
The modified annotation for this parameter
https://docs.python.org/3/glossary.html#term-annotation
annot_str: str
string for this annotation (used for source code generation)
for instance if annotation is `float`, just use "float"
"""
self.tuned_annotations[pname] = tuned_annotations
self.tuned_annotations_str[pname] = annot_str
def make_partial(self, pname, val):
""" Remove the parameter from the signature (hence from the
GUI-settable parameters) and impose its value to be val.
Implementation note: Value of the partials to be considered by the
caller (caller responsability) before actually calling the GUItemplate
"""
self.partial_vals[pname] = val
def signature(self):
"""
Signature used as a base for the GUI-display
Returns:
--------
sgn, taking into account
- params with modified default value (through set_default)
- params with modified annotation (through set_annotation)
- params suppressed as a result of partial
"""
base_sgn = inspect.signature(self.__call__)
my_defaults = self.tuned_defaults
my_annots = self.tuned_annotations
to_del = self.partial_vals.keys()
sgn_params = []
for p_name, param in base_sgn.parameters.items():
if p_name in to_del:
# Dropping this parameter -> skip to next item
continue
if p_name in my_defaults.keys():
new_default = my_defaults[p_name]
else:
new_default = param.default
if p_name in my_annots.keys():
new_annot = my_annots[p_name]
else:
new_annot = param.annotation
new_param = param.replace(
default=new_default,
annotation=new_annot,
)
sgn_params += [new_param]
sgn = inspect.Signature(
parameters=sgn_params,
return_annotation=base_sgn.return_annotation
)
return sgn
def getsource(self, movie=False):
""" Returns the source code defining the function
"""
base_source = textwrap.dedent(inspect.getsource(self.__call__))
func_name, func_params, func_body = self.split_source(base_source)
if movie:
look_for, replace_by = self.movie_source_modifiers()
cut_index = func_body.find(look_for)
func_body = func_body[:cut_index] + replace_by
my_defaults = self.tuned_defaults
my_annots = self.tuned_annotations_str
my_vals = self.partial_vals
# func name: changed from __call__ to "plot" or "get_plotter"
func_name = "def get_plotter(" if movie else "def plot("
# func parameters:
str_params = ""
for p_name, v in func_params.items():
if p_name == "self":
continue
# print("** v<", v, ">")
decl, val = v.split("=", 1) # dec -> <pname: annotation> / val -> <val>
if p_name in my_defaults.keys():
val = " " + Code_writer.var_tocode(
my_defaults[p_name], indent=1)
if p_name in my_vals.keys():
val = " " + Code_writer.var_tocode(
my_vals[p_name], indent=1)
if p_name in my_annots.keys():
decl = "\n " + p_name + " : " + my_annots[p_name] + " "
v = decl + "=" + val + ","
str_params += v
# Remove final ","
str_params = str_params[:-1]
# Remove final "\n" if applicable (due to delimitation of last item)
# Note: https://docs.python.org/2/library/stdtypes.html#str.splitlines
last_char = str_params[-1]
if len(str.splitlines(last_char)[0]) == 0:
str_params = str_params[:-1]
# Adds additional "batch" parameters:
str_params += ",\n batch_params=batch_params\n"
return func_name + str_params + func_body
def split_source(self, source):
"""
Parameters
----------
source: string
Function source code to be parsed
Returns
-------
(func_name, func_params, func_body)
func_name: str
The extracted string "def func_name"
func_params: mapping
param -> param_txt where param_txt is the text
describing the parameter in the function source code ie:
"param_x: type = xxx" (as delimited by quotes)
func_body: str
the func body including the closing parameters parenthesis
"""
base_sgn = inspect.signature(self.__class__.__call__)
return split_source(source, base_sgn)
#==============================================================================
# A list of non Gui-editable parameters, which we want to be able to pass
# as parameters in batch mode
batch_params = {}
[docs]
class std_zooming(GUItemplate):
[docs]
def __init__(self, fractal):
"""
A generic zooming function to explore standard or perturbation Fractals,
intended for use with a GUI `fs.gui.guimodel.Fractal_GUI`
Compatible with :
- holomorphic or non-holomorphic (Burning-ship & al) variants
- optional fieldlines
- optional shading
- optional interior plots based on cycle attractivity & order
- optional deepzoom implementation
Parameters
----------
fractal: `fs.Fractal`
The fractal object to explore
Notes
-----
.. note::
A typical use case is show below (see also the interactive examples from
the :doc:`../../examples/index/` section):
::
fractal = fsm.Perturbation_mandelbrot(plot_dir)
zooming = fs.gui.guitemplates.std_zooming(fractal)
gui = fs.gui.guimodel.Fractal_GUI(zooming)
gui.show()
"""
super().__init__(fractal)
badges = (
"holomorphic", "implements_dzndc", "implements_fieldlines",
"implements_newton", "implements_Milnor",
"implements_interior_detection", "implements_deepzoom"
)
for attr in badges:
setattr(self, attr, getattr(fractal, attr, False))
self.connect_image_params = {
"image_param": "calc_name"
}
if self.implements_deepzoom:
self.connect_mouse_params = {
"x": "x", "y": "y", "dx": "dx",
"xy_ratio": "xy_ratio", "dps": "dps"
}
else:
self.connect_mouse_params = {
"x": "x", "y": "y", "dx": "dx",
"xy_ratio": "xy_ratio", "dps": None
}
if not(self.holomorphic):
# Connects also the zooming skew parameters
self.connect_mouse_params.update({
"has_skew": "has_skew",
"skew_00": "skew_00",
"skew_01": "skew_01",
"skew_10": "skew_10",
"skew_11": "skew_11"
})
if not(self.implements_newton):
# Delete all parameters associated with Newton
self.make_partial("compute_newton", False)
self.make_partial("_3", None)
self.make_partial("max_order", None)
self.make_partial("max_newton", None)
self.make_partial("eps_newton_cv", None)
self.make_partial("_7", None)
self.make_partial("int_layer", None)
self.make_partial("colormap_int", None)
self.make_partial("cmap_func_int", None)
self.make_partial("zmin_int", None)
self.make_partial("zmax_int", None)
self.make_partial("lighting_int", None)
if self.holomorphic:
# Delete all parameters associated with skew transform
self.make_partial("_1b", None)
self.make_partial("has_skew", False)
self.make_partial("skew_00", 1.)
self.make_partial("skew_01", 0.)
self.make_partial("skew_10", 0.)
self.make_partial("skew_11", 1.)
if self.implements_dzndc != "user":
self.make_partial("calc_dzndc", False)
if not(self.implements_fieldlines):
# Delete all parameters associated with fieldlines
self.make_partial("_6", None)
self.make_partial("has_fieldlines", False)
self.make_partial("fieldlines_func", None)
self.make_partial("fieldlines_kind", None)
self.make_partial("fieldlines_zmin", None)
self.make_partial("fieldlines_zmax", None)
self.make_partial("backshift", None)
self.make_partial("n_iter", None)
self.make_partial("swirl", None)
self.make_partial("damping_ratio", None)
self.make_partial("twin_intensity", None)
if self.implements_Milnor:
self.set_annotation(
"shading_kind",
typing.Literal["potential", "Milnor"],
'typing.Literal["potential", "Milnor"]'
)
if self.implements_interior_detection == "no":
self.make_partial("interior_detect", False)
self.make_partial("epsilon_stationnary", None)
elif self.implements_interior_detection == "always":
self.make_partial("interior_detect", True)
elif self.implements_interior_detection == "user":
pass
else:
raise ValueError(
"Unexpected value for GUI interior_detection:"
f"{self.implements_interior_detection}"
)
if self.implements_deepzoom:
self.set_annotation("x", mpmath.mpf, "mpmath.mpf")
self.set_annotation("y", mpmath.mpf, "mpmath.mpf")
self.set_annotation("dx", mpmath.mpf, "mpmath.mpf")
self.set_default("x", "0.0")
self.set_default("y", "0.0")
self.set_default("dx", "10.0")
else:
self.make_partial("dps", None)
def __call__(
self,
fractal: fs.Fractal=None,
calc_name: str="std_zooming_calc",
_1: fs.gui.collapsible_separator="Zoom parameters",
x: float = 0.0,
y: float = 0.0,
dx: float = 10.0,
dps: int = 16,
xy_ratio: float = 1.0,
theta_deg: float = 0.0,
nx: int = 600,
_1b: fs.gui.collapsible_separator = (
"Skew parameters /!\ Re-run when modified!"
),
has_skew: bool = False,
skew_00: float = 1.,
skew_01: float = 0.,
skew_10: float = 0.,
skew_11: float = 1.,
_2: fs.gui.collapsible_separator="Calculation parameters",
max_iter: int = 5000,
M_divergence: float = 1000.,
interior_detect: bool = True,
epsilon_stationnary: float = 0.001,
calc_dzndc: bool = True,
_3: fs.gui.collapsible_separator = "Newton parameters",
compute_newton: bool = True,
max_order: int = 1500,
max_newton: int = 20,
eps_newton_cv: float =1.e-8,
_4: fs.gui.collapsible_separator="Plotting parameters: base field",
base_layer: typing.Literal[
"continuous_iter",
"distance_estimation"
]="continuous_iter",
colormap: fs.colors.Fractal_colormap=(
fs.colors.cmap_register["classic"]
),
cmap_func: fs.numpy_utils.Numpy_expr = (
fs.numpy_utils.Numpy_expr("x", "np.log(x)")
),
zmin: float = 0.0,
zmax: float = 5.0,
zshift: float = -1.0,
mask_color: fs.colors.Color=(0.1, 0.1, 0.1, 1.0),
_7: fs.gui.collapsible_separator="Plotting parameters: Newton field",
int_layer: typing.Literal[
"attractivity", "order", "attr / order"
]="attractivity",
colormap_int: fs.colors.Fractal_colormap = (
fs.colors.Fractal_colormap(
colors=[
[1. , 1. , 1. ],
[0.16862746, 0.16862746, 0.16862746],
[0. , 0. , 0. ]
],
kinds=['Lch', 'Lch'],
grad_npts=[8, 8],
grad_funcs=['x', 'x'],
extent='mirror'
)
),
cmap_func_int: fs.numpy_utils.Numpy_expr = (
fs.numpy_utils.Numpy_expr("x", "x")
),
zmin_int: float = 0.0,
zmax_int: float = 1.0,
_5: fs.gui.collapsible_separator = "Plotting parameters: shading",
has_shading: bool = True,
shading_kind: typing.Literal["potential"] = "potential",
lighting: Blinn_lighting = (
fs.colors.lighting_register["glossy"]
),
lighting_int: Blinn_lighting = (
fs.colors.lighting_register["glossy"]
),
max_slope: float = 60.,
_6: fs.gui.collapsible_separator = "Plotting parameters: field lines",
has_fieldlines: bool = False,
fieldlines_func: fs.numpy_utils.Numpy_expr = (
fs.numpy_utils.Numpy_expr("x", "x")
),
fieldlines_kind: typing.Literal["overlay", "twin"] = "overlay",
fieldlines_zmin: float = -1.0,
fieldlines_zmax: float = 1.0,
backshift: int = 3,
n_iter: int = 4,
swirl: float = 0.,
damping_ratio: float = 0.8,
twin_intensity: float = 0.1,
_8: fs.gui.collapsible_separator="High-quality rendering options",
final_render: bool=False,
supersampling: fs.core.supersampling_type = "None",
jitter: bool = False,
recovery_mode: bool = False,
_9: fs.gui.collapsible_separator="Extra outputs",
output_masks: bool = False,
output_normals: bool = False,
output_heightmaps: bool = False,
hmap_mask: float = 0.,
int_hmap_mask: float = 0.,
_10: fs.gui.collapsible_separator="General settings",
log_verbosity: typing.Literal[fs.log.verbosity_enum
] = "debug @ console + log",
enable_multithreading: bool = True,
inspect_calc: bool = False,
no_newton: bool = False,
postproc_dtype: typing.Literal["float32", "float64"] = "float32"
):
fs.settings.log_directory = os.path.join(fractal.directory, "log")
fs.set_log_handlers(verbosity=log_verbosity)
fs.settings.enable_multithreading = enable_multithreading
fs.settings.inspect_calc = inspect_calc
fs.settings.no_newton = no_newton
fs.settings.postproc_dtype = postproc_dtype
zoom_kwargs = {
"x": x,
"y": y,
"dx": dx,
"nx": nx,
"xy_ratio": xy_ratio,
"theta_deg": theta_deg,
"has_skew": has_skew,
"skew_00": skew_00,
"skew_01": skew_01,
"skew_10": skew_10,
"skew_11": skew_11,
"projection": batch_params.get(
"projection", fs.projection.Cartesian()
)
}
if fractal.implements_deepzoom:
zoom_kwargs["precision"] = dps
fractal.zoom(**zoom_kwargs)
calc_std_div_kw = {
"calc_name": calc_name,
"subset": None,
"max_iter": max_iter,
"M_divergence": M_divergence,
}
if fractal.implements_dzndc == "user":
calc_std_div_kw["calc_dzndc"] = calc_dzndc
if shading_kind == "Milnor":
calc_std_div_kw["calc_d2zndc2"] = True
if has_fieldlines:
calc_orbit = (backshift > 0)
calc_std_div_kw["calc_orbit"] = calc_orbit
calc_std_div_kw["backshift"] = backshift
if fractal.implements_interior_detection == "always":
calc_std_div_kw["epsilon_stationnary"] = epsilon_stationnary
elif fractal.implements_interior_detection == "user":
calc_std_div_kw["interior_detect"] = interior_detect
calc_std_div_kw["epsilon_stationnary"] = epsilon_stationnary
fractal.calc_std_div(**calc_std_div_kw)
# Run the calculation for the interior points - if wanted
if compute_newton:
interior = Fractal_array(
fractal, calc_name, "stop_reason", func= "x != 1"
)
fractal.newton_calc(
calc_name="interior",
subset=interior,
known_orders=None,
max_order=max_order,
max_newton=max_newton,
eps_newton_cv=eps_newton_cv,
)
pp = Postproc_batch(fractal, calc_name)
if base_layer == "continuous_iter":
pp.add_postproc(base_layer, Continuous_iter_pp())
if output_heightmaps:
pp.add_postproc("base_hmap", Continuous_iter_pp())
elif base_layer == "distance_estimation":
pp.add_postproc("continuous_iter", Continuous_iter_pp())
pp.add_postproc(base_layer, DEM_pp())
if output_heightmaps:
pp.add_postproc("base_hmap", DEM_pp())
if has_fieldlines:
pp.add_postproc(
"fieldlines",
Fieldlines_pp(n_iter, swirl, damping_ratio)
)
else:
fieldlines_kind = "None"
pp.add_postproc("interior", Raw_pp("stop_reason", func="x != 1"))
if compute_newton:
pp_int = Postproc_batch(fractal, "interior")
if int_layer == "attractivity":
pp_int.add_postproc(int_layer, Attr_pp())
if output_heightmaps:
pp_int.add_postproc("interior_hmap", Attr_pp())
elif int_layer == "order":
pp_int.add_postproc(int_layer, Raw_pp("order"))
if output_heightmaps:
pp_int.add_postproc("interior_hmap", Raw_pp("order"))
elif int_layer == "attr / order":
pp_int.add_postproc(int_layer, Attr_pp(scale_by_order=True))
if output_heightmaps:
pp_int.add_postproc(
"interior_hmap", Attr_pp(scale_by_order=True)
)
# Set of unknown points
pp_int.add_postproc(
"unknown", Raw_pp("stop_reason", func="x == 0")
)
pps = [pp, pp_int]
else:
pps = pp
if has_shading:
pp.add_postproc("DEM_map", DEM_normal_pp(kind=shading_kind))
if compute_newton:
pp_int.add_postproc("attr_map", Attr_normal_pp())
plotter = fs.Fractal_plotter(
pps,
final_render=final_render,
supersampling=supersampling,
jitter=jitter,
recovery_mode=recovery_mode
)
# The mask values & curves for heighmaps
r1 = min(hmap_mask, 0.)
r2 = max(hmap_mask, 1.)
dr = r2 - r1
hmap_curve = lambda x : (np.clip(x, 0., 1.) - r1) / dr
r1 = min(int_hmap_mask, 0.)
r2 = max(int_hmap_mask, 1.)
dr = r2 - r1
int_hmap_curve = lambda x : (np.clip(x, 0., 1.) - r1) / dr
# The layers
plotter.add_layer(Bool_layer("interior", output=output_masks))
if compute_newton:
plotter.add_layer(Bool_layer("unknown", output=output_masks))
if fieldlines_kind == "twin":
plotter.add_layer(Virtual_layer(
"fieldlines", func=fieldlines_func, output=False
))
elif fieldlines_kind == "overlay":
plotter.add_layer(Grey_layer(
"fieldlines", func=fieldlines_func,
probes_z=[fieldlines_zmin, fieldlines_zmax],
output=False
))
if has_shading:
plotter.add_layer(Normal_map_layer(
"DEM_map", max_slope=max_slope, output=output_normals
))
plotter["DEM_map"].set_mask(plotter["interior"])
if compute_newton:
plotter.add_layer(Normal_map_layer(
"attr_map", max_slope=90, output=output_normals
))
if base_layer != 'continuous_iter':
plotter.add_layer(
Virtual_layer("continuous_iter", func=None, output=False)
)
plotter.add_layer(Color_layer(
base_layer,
func=cmap_func,
colormap=colormap,
probes_z=[zmin + zshift, zmax + zshift],
output=True)
)
if output_heightmaps:
plotter.add_layer(Disp_layer(
"base_hmap",
func=cmap_func,
curve=hmap_curve,
probes_z=[zmin + zshift, zmax + zshift],
output=True
))
if compute_newton:
plotter.add_layer(Color_layer(
int_layer,
func=cmap_func_int,
colormap=colormap_int,
probes_z=[zmin_int, zmax_int],
output=False))
plotter[int_layer].set_mask(plotter["unknown"],
mask_color=mask_color)
if output_heightmaps:
plotter.add_layer(Disp_layer(
"interior_hmap",
func=cmap_func,
curve=int_hmap_curve,
probes_z=[zmin_int, zmax_int],
output=True
))
plotter["interior_hmap"].set_mask(
plotter["unknown"],
mask_color=(int_hmap_mask,)
)
if fieldlines_kind == "twin":
plotter[base_layer].set_twin_field(
plotter["fieldlines"], twin_intensity
)
elif fieldlines_kind == "overlay":
overlay_mode = Overlay_mode("tint_or_shade", pegtop=1.0)
plotter[base_layer].overlay(plotter["fieldlines"], overlay_mode)
if has_shading:
plotter[base_layer].shade(plotter["DEM_map"], lighting)
if compute_newton:
plotter[int_layer].shade(plotter["attr_map"], lighting_int)
plotter["attr_map"].set_mask(plotter["unknown"],
mask_color=(0., 0., 0., 0.))
if compute_newton:
# Overlay : alpha composite with "interior" layer ie, where it is not
# masked, we take the value of the "attr" layer
overlay_mode = Overlay_mode(
"alpha_composite",
alpha_mask=plotter["interior"],
inverse_mask=True
)
plotter[base_layer].overlay(plotter[int_layer], overlay_mode=overlay_mode)
else:
plotter[base_layer].set_mask(
plotter["interior"], mask_color=mask_color
)
if output_heightmaps:
plotter["base_hmap"].set_mask(
plotter["interior"], mask_color=(hmap_mask,)
)
plotter.plot()
# Renaming output to match expected from the Fractal GUI
layer = plotter[base_layer]
file_name = "{}_{}".format(type(layer).__name__, layer.postname)
src_path = os.path.join(fractal.directory, file_name + ".png")
dest_path = os.path.join(fractal.directory, calc_name + ".png")
if os.path.isfile(dest_path):
os.unlink(dest_path)
os.link(src_path, dest_path)
def movie_source_modifiers(self):
""" Return the mods to source code needed for a movie making instance
-> do not plot the image
-> instead, simply return the plotter + base image name
"""
look_for = "plotter.plot()"
replace_by = """\n return plotter, plotter[base_layer].postname
"""
return look_for, replace_by