diff --git a/.gitignore b/.gitignore index ea4a0dade..b97c00845 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,9 @@ # /plugins_src/autouv/ /plugins_src/autouv/autouv_en.lang +# /plugins_src/scripting/docs/generated/ +/plugins_src/scripting/docs/generated/* + # /plugins_src/fbx/ /plugins_src/fbx/*.o /plugins_src/fbx/*.obj diff --git a/plugins/scripting/README b/plugins/scripting/README new file mode 100644 index 000000000..61eaea1b1 --- /dev/null +++ b/plugins/scripting/README @@ -0,0 +1 @@ +This directory contains the scripting plugin. diff --git a/plugins/scripting/wpc_scripting_shapes_init/README b/plugins/scripting/wpc_scripting_shapes_init/README new file mode 100644 index 000000000..a5d1aa3b3 --- /dev/null +++ b/plugins/scripting/wpc_scripting_shapes_init/README @@ -0,0 +1 @@ +This directory contains files for the scripting plugin. diff --git a/plugins/scripting/wpc_scripting_shapes_init/py/README b/plugins/scripting/wpc_scripting_shapes_init/py/README new file mode 100644 index 000000000..06724e6c5 --- /dev/null +++ b/plugins/scripting/wpc_scripting_shapes_init/py/README @@ -0,0 +1 @@ +This directory contains the python files for scripting. diff --git a/plugins/scripting/wpc_scripting_shapes_init/scm/README b/plugins/scripting/wpc_scripting_shapes_init/scm/README new file mode 100644 index 000000000..cedf160c8 --- /dev/null +++ b/plugins/scripting/wpc_scripting_shapes_init/scm/README @@ -0,0 +1 @@ +This directory contains the scheme files for scripting. diff --git a/plugins_src/Makefile b/plugins_src/Makefile index e94ad5933..03ac52f0f 100644 --- a/plugins_src/Makefile +++ b/plugins_src/Makefile @@ -51,6 +51,7 @@ subdirs: (cd primitives; $(MAKE)) (cd commands; $(MAKE)) (cd autouv; $(MAKE)) + (cd scripting; $(MAKE)) template: opt $(ERL) -pa $(WINGS_INTL) -noinput -run tools generate_template_files $(EBIN) @@ -64,6 +65,7 @@ subdirs_lang: (cd primitives; $(MAKE) lang) (cd commands; $(MAKE) lang) (cd autouv; $(MAKE) lang) + (cd scripting; $(MAKE) lang) clean: subdirs_clean rm -f $(TARGET_FILES) @@ -74,6 +76,7 @@ subdirs_clean: (cd primitives; $(MAKE) clean) (cd commands; $(MAKE) clean) (cd autouv; $(MAKE) clean) + (cd scripting; $(MAKE) clean) $(EBIN)/%.beam: $(ESRC)/%.erl $(ERLC) $(ERL_COMPILE_FLAGS) -o$(EBIN) $< diff --git a/plugins_src/scripting/Makefile b/plugins_src/scripting/Makefile new file mode 100644 index 000000000..68bf5dc12 --- /dev/null +++ b/plugins_src/scripting/Makefile @@ -0,0 +1,125 @@ +# +# Makefile -- +# +# Makefile for building plug-ins for scripting. +# +# Copyright (c) 2001-2013 Bjorn Gustavsson +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +# $Id: Makefile,v 1.14 2006/08/02 22:44:40 antoneos Exp $ +# +include ../../erl.mk + +.SUFFIXES: .erl .jam .beam .yrl .xrl .bin .mib .hrl .sgml .html .ps .3 .1 \ + .fig .dvi .tex .class .java .pdf .psframe .pscrop + +ESRC=. +WINGS_INTL=../../intl_tools +EBIN=../../plugins/scripting +WINGS_TOP=../../.. +WINGS_E3D=../../e3d + +ifeq ($(TYPE),debug) +TYPE_FLAGS=-DDEBUG +else +TYPE_FLAGS= +endif + +MODULES= \ + wpc_scripting_shapes \ + scripting_engines + +INIT_SCRIPT_SRC=$(ESRC)/init_scripts +INIT_SCRIPT_EBIN=$(EBIN)/wpc_scripting_shapes_init +INIT_SCRIPT_PY_SRC=$(INIT_SCRIPT_SRC)/py +INIT_SCRIPT_PY_EBIN=$(INIT_SCRIPT_EBIN)/py +INIT_SCRIPT_SCM_SRC=$(INIT_SCRIPT_SRC)/scm +INIT_SCRIPT_SCM_EBIN=$(INIT_SCRIPT_EBIN)/scm + +TARGET_FILES= $(MODULES:%=$(EBIN)/%.beam) + + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +ERL_COMPILE_FLAGS += -Werror +nowarn_match_float_zero -I $(WINGS_TOP) \ + $(TYPE_FLAGS) -pa $(WINGS_INTL) +debug_info +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +opt debug: + $(MAKE) TYPE=$@ common + +template: opt + $(ERL) -pa $(WINGS_INTL) -noinput -run tools generate_template_files $(EBIN) + +lang: template + cp *.lang $(EBIN) + $(ERL) -pa $(WINGS_INTL) -noinput -run tools diff_lang_files $(EBIN) + +common: $(TARGET_FILES) $(INIT_SCRIPT_EBIN) subdirs + +subdirs: + (cd docs; $(MAKE)) + +clean: + rm -f $(TARGET_FILES) + rm -f core + (cd docs; $(MAKE) clean) + +SH?=sh + +$(EBIN)/%.beam: $(ESRC)/%.erl + $(ERLC) $(ERL_COMPILE_FLAGS) -o$(EBIN) $< + +PY_FILES= init w3d_e3d w3d_int w3d_newshape +PY_FILES_1=$(PY_FILES:%=$(INIT_SCRIPT_PY_EBIN)/%.py) + +SCM_FILES= init_env_csi init_env_gauche +SCM_FILES_1=$(SCM_FILES:%=$(INIT_SCRIPT_SCM_EBIN)/%.scm) + +$(INIT_SCRIPT_EBIN): $(INIT_SCRIPT_EBIN)/callable.conf \ + $(INIT_SCRIPT_EBIN)/defaults.conf \ + $(INIT_SCRIPT_EBIN)/python.script-init-conf \ + $(INIT_SCRIPT_EBIN)/scheme.script-init-conf \ + $(INIT_SCRIPT_PY_EBIN)/w3d_we.py \ + $(INIT_SCRIPT_SCM_EBIN)/init.scm \ + $(PY_FILES_1) \ + $(SCM_FILES_1) + + +$(INIT_SCRIPT_EBIN)/%.conf: $(INIT_SCRIPT_SRC)/%.conf + cp $< $(INIT_SCRIPT_EBIN)/ +$(INIT_SCRIPT_EBIN)/%.script-init-conf: $(INIT_SCRIPT_SRC)/%.script-init-conf + cp $< $(INIT_SCRIPT_EBIN)/ +$(INIT_SCRIPT_PY_EBIN)/%.py: $(INIT_SCRIPT_PY_SRC)/%.py + cp $< $(INIT_SCRIPT_PY_EBIN)/ +$(INIT_SCRIPT_SCM_EBIN)/%.scm: $(INIT_SCRIPT_SCM_SRC)/%.scm + cp $< $(INIT_SCRIPT_SCM_EBIN)/ + +# Generate w3d_we.py file from callable.conf +$(INIT_SCRIPT_PY_EBIN)/w3d_we.py: $(INIT_SCRIPT_PY_SRC)/w3d_we.1.py \ + $(INIT_SCRIPT_SRC)/callable.conf \ + $(INIT_SCRIPT_SRC)/py-modnames + $(SH) ./tools/gen-init-we-script.sh \ + "$(INIT_SCRIPT_PY_SRC)/w3d_we.1.py" \ + - \ + "$(INIT_SCRIPT_SRC)/callable.conf" \ + "$(INIT_SCRIPT_SRC)/py-modnames" py \ + > "$(INIT_SCRIPT_PY_EBIN)/w3d_we.py" + +# Generate init.scm file from callable.conf +$(INIT_SCRIPT_SCM_EBIN)/init.scm: $(INIT_SCRIPT_SCM_SRC)/init.1.scm \ + $(INIT_SCRIPT_SRC)/callable.conf \ + $(INIT_SCRIPT_SRC)/scm-modnames \ + $(INIT_SCRIPT_SCM_SRC)/init.2.scm + $(SH) ./tools/gen-init-we-script.sh \ + "$(INIT_SCRIPT_SCM_SRC)/init.1.scm" \ + "$(INIT_SCRIPT_SCM_SRC)/init.2.scm" \ + "$(INIT_SCRIPT_SRC)/callable.conf" \ + "$(INIT_SCRIPT_SRC)/scm-modnames" scm \ + > "$(INIT_SCRIPT_SCM_EBIN)/init.scm" + diff --git a/plugins_src/scripting/docs/Makefile b/plugins_src/scripting/docs/Makefile new file mode 100644 index 000000000..b6466d6d4 --- /dev/null +++ b/plugins_src/scripting/docs/Makefile @@ -0,0 +1,108 @@ +# +# Makefile -- +# +# Makefile for building scripting documentation +# +# Copyright (c) 2025 Edward Blake +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# + +AWK?=awk +MKDIR?=mkdir -p +PACK_MANUAL?=../../../tools/pack_manual +ESCRIPT?=escript + +FILES= \ + en/INDEX \ + en/README.txt \ + en/wscr/intro.txt \ + en/wscr/langfiles.txt \ + en/wscr/pluginscripts.txt \ + en/wscr/query.txt \ + en/wscr/scriptfolders.txt \ + en/wscr/directives/type.txt \ + en/wscr/directives/name.txt \ + en/wscr/directives/mode.txt \ + en/wscr/directives/desc.txt \ + en/wscr/directives/include.txt \ + en/wscr/directives/params_title.txt \ + en/wscr/directives/params_templates.txt \ + en/wscr/directives/template.txt \ + en/wscr/directives/params.txt \ + en/wscr/directives/param.txt \ + en/wscr/directives/extensions.txt \ + en/wscr/directives/params_init.txt \ + en/wscr/directives/params_set.txt \ + en/wscr/directives/export_param.txt \ + en/wscr/directives/import_param.txt \ + en/wscr/directives/adv_param.txt \ + en/wscr/directives/params_preview.txt \ + en/wscr/directives/command_changes.txt \ + en/wscr/directives/command_inputs.txt \ + en/wscr/directives/extra_file.txt \ + en/wscr/directives/script_param.txt \ + en/wscr/directives/hradio.txt \ + en/wscr/directives/menu.txt \ + en/wscr/directives/browse.txt \ + en/wscr/directives/checkbox.txt \ + en/wscr/directives/vradio.txt \ + en/scheme/command_scripts.txt \ + en/scheme/exporter_scripts.txt \ + en/scheme/importer_scripts.txt \ + en/scheme/intro.txt \ + en/scheme/langres.txt \ + en/scheme/new_shape_scripts.txt \ + en/scheme/functions/e3d_face.txt \ + en/scheme/functions/e3d_file.txt \ + en/scheme/functions/e3d_image.txt \ + en/scheme/functions/e3d_mesh.txt \ + en/scheme/functions/e3d_object.txt \ + en/python/command_scripts.txt \ + en/python/exporter_scripts.txt \ + en/python/importer_scripts.txt \ + en/python/intro.txt \ + en/python/langres.txt \ + en/python/new_shape_scripts.txt \ + en/python/functions/e3d_face.txt \ + en/python/functions/e3d_file.txt \ + en/python/functions/e3d_image.txt \ + en/python/functions/e3d_mesh.txt \ + en/python/functions/e3d_object.txt \ + en/python/functions/list_of_arrays.txt \ + en/python/functions/list_of_tuples.txt \ + en/python/functions/material.txt \ + en/python/functions/material_maps.txt \ + en/python/functions/material_opengl.txt \ + en/python/functions/new_shape.txt \ + en/python/functions/output_list.txt + +SCRIPTINITDIR=../../../plugins/scripting/wpc_scripting_shapes_init + +all: ${SCRIPTINITDIR}/scripting-reference-en.manual + +clean: + rm generated/en/scm-we.txt + rm generated/en/py-we.txt + rmdir generated/en + rmdir generated + + +generated/en: + $(MKDIR) generated/en + +generated/en/scm-we.txt: generated/en funs + $(AWK) -v type=scm -f ../tools/gen-funs-doc.awk < funs > generated/en/scm-we.txt + +generated/en/py-we.txt: generated/en funs + $(AWK) -v type=py -f ../tools/gen-funs-doc.awk < funs > generated/en/py-we.txt + +${SCRIPTINITDIR}/scripting-reference-en.manual: $(FILES) \ + generated/en/scm-we.txt \ + generated/en/py-we.txt + $(ESCRIPT) $(PACK_MANUAL) en \ + --out ${SCRIPTINITDIR}/scripting-reference-en.manual + + + diff --git a/plugins_src/scripting/docs/en/INDEX b/plugins_src/scripting/docs/en/INDEX new file mode 100644 index 000000000..0bcb3942d --- /dev/null +++ b/plugins_src/scripting/docs/en/INDEX @@ -0,0 +1,94 @@ +{info,[ + {title, "Scripting Reference"}, + {language, "en"} +]}. +{txt, "README", "README.txt"}. +{"Scripting Information",[ + {"Introduction",[ + {txt, "Introduction", "wscr/intro.txt"}, + {txt, "Script Folders", "wscr/scriptfolders.txt"}, + {txt, "Plugin Scripts", "wscr/pluginscripts.txt"}, + {txt, "Language files", "wscr/langfiles.txt"}, + {txt, "Query Mini Language", "wscr/query.txt"} + ]}, + {"Directive Reference",[ + {txt, "type Directive", "wscr/directives/type.txt"}, + {txt, "name Directive", "wscr/directives/name.txt"}, + {txt, "mode Directive", "wscr/directives/mode.txt"}, + {txt, "desc Directive", "wscr/directives/desc.txt"}, + {txt, "include Directive", "wscr/directives/include.txt"}, + {txt, "params_title Directive", "wscr/directives/params_title.txt"}, + {txt, "params_templates Directive", "wscr/directives/params_templates.txt"}, + {txt, "template Directive", "wscr/directives/template.txt"}, + {txt, "params Directive", "wscr/directives/params.txt"}, + {txt, "param Directive", "wscr/directives/param.txt"}, + {txt, "extensions Directive", "wscr/directives/extensions.txt"}, + {txt, "params_init Directive", "wscr/directives/params_init.txt"}, + {txt, "params_set Directive", "wscr/directives/params_set.txt"}, + {txt, "export_param Directive", "wscr/directives/export_param.txt"}, + {txt, "import_param Directive", "wscr/directives/import_param.txt"}, + {txt, "adv_param Directive", "wscr/directives/adv_param.txt"}, + {txt, "params_preview Directive", "wscr/directives/params_preview.txt"}, + {txt, "script_param Directive", "wscr/directives/script_param.txt"}, + {txt, "extra_file Directive", "wscr/directives/extra_file.txt"}, + {txt, "command_inputs Directive", "wscr/directives/command_inputs.txt"}, + {txt, "command_changes Directive", "wscr/directives/command_changes.txt"}, + {txt, "hradio Directive", "wscr/directives/hradio.txt"}, + {txt, "menu Directive", "wscr/directives/menu.txt"}, + {txt, "browse Directive", "wscr/directives/browse.txt"}, + {txt, "checkbox Directive", "wscr/directives/checkbox.txt"}, + {txt, "vradio Directive", "wscr/directives/vradio.txt"} + ]} +]}. +{"Python",[ + {"Introduction",[ + {txt, "Online Resources for the Python Language", "python/langres.txt"}, + {txt, "Python Intro", "python/intro.txt"}, + {txt, "New Shape Scripts", "python/new_shape_scripts.txt"}, + {txt, "Command Scripts", "python/command_scripts.txt"}, + {txt, "Importer Scripts", "python/importer_scripts.txt"}, + {txt, "Exporter Scripts", "python/exporter_scripts.txt"} + ]}, + {"OutputList Function Reference",[ + {txt, "OutputList", "python/functions/output_list.txt"} + ]}, + {"NewShape (w3d_newshape) Function Reference",[ + {txt, "ListOfArrays", "python/functions/list_of_arrays.txt"}, + {txt, "ListOfTuples", "python/functions/list_of_tuples.txt"}, + {txt, "NewShape", "python/functions/new_shape.txt"} + ]}, + {"E3D (w3d_e3d) Function Reference",[ + {txt, "E3DFace", "python/functions/e3d_face.txt"}, + {txt, "E3DMesh", "python/functions/e3d_mesh.txt"}, + {txt, "E3DObject", "python/functions/e3d_object.txt"}, + {txt, "E3DImage", "python/functions/e3d_image.txt"}, + {txt, "MaterialMaps", "python/functions/material_maps.txt"}, + {txt, "MaterialOpenGLAttributes", "python/functions/material_opengl.txt"}, + {txt, "Material", "python/functions/material.txt"}, + {txt, "E3DFile", "python/functions/e3d_file.txt"} + ]}, + {"We (w3d_we) Function Reference",[ + {txt, "we", "../generated/en/py-we.txt"} + ]} +]}. +{"Scheme",[ + {"Introduction",[ + {txt, "Online Resources for the Scheme Language", "scheme/langres.txt"}, + {txt, "Scheme Intro", "scheme/intro.txt"}, + {txt, "New Shape Scripts", "scheme/new_shape_scripts.txt"}, + {txt, "Command Scripts", "scheme/command_scripts.txt"}, + {txt, "Importer Scripts", "scheme/importer_scripts.txt"}, + {txt, "Exporter Scripts", "scheme/exporter_scripts.txt"} + ]}, + {"E3D Function Reference",[ + {txt, "e3d_face", "scheme/functions/e3d_face.txt"}, + {txt, "e3d_mesh", "scheme/functions/e3d_mesh.txt"}, + {txt, "e3d_object", "scheme/functions/e3d_object.txt"}, + {txt, "e3d_file", "scheme/functions/e3d_file.txt"}, + {txt, "e3d_image", "scheme/functions/e3d_image.txt"} + ]}, + {"We Function Reference",[ + {txt, "we", "../generated/en/scm-we.txt"} + ]} +]}. + diff --git a/plugins_src/scripting/docs/en/README.txt b/plugins_src/scripting/docs/en/README.txt new file mode 100644 index 000000000..4761a2a52 --- /dev/null +++ b/plugins_src/scripting/docs/en/README.txt @@ -0,0 +1,18 @@ +Scripting Plugin for Wings3D + +This reference contains three sections: + +Script Information (.wscr) + +Scheme Scripting + +Python Scripting + + +CONTRIBUTORS FOR DOCUMENTATION + +Edward Blake has written the documentation for the first +release of the scripting plugin. + + + diff --git a/plugins_src/scripting/docs/en/python/command_scripts.txt b/plugins_src/scripting/docs/en/python/command_scripts.txt new file mode 100644 index 000000000..cc1bb05a9 --- /dev/null +++ b/plugins_src/scripting/docs/en/python/command_scripts.txt @@ -0,0 +1,230 @@ +Command Scripts + +Command scripts are available when right clicking on a selected object. + +The "points" extra parameter is needed to access the list of vertices, which come as a vertice number and a 3 dimensional position. + +
+import w3d_int
+
+def main(params, params_by_key, extra_params):
+    points = w3d_int.Points()
+    points.load_from(extra_params["points"])
+
+    ##
+    ## ...
+    ##
+
+    ## Return new points positions
+    return [w3d_int.SetPoints(points)]
+
+w3d_main_function = main
+
+ +Don't forget to assign the 'main' function to 'w3d_main_function'. + +The following script shifts all the vertice positions of a selected object by 0.5 in all axis: + +
+import w3d_int
+
+def command(params, params_by_key, extra_params):
+    op = extra_params["op"]
+    points = w3d_int.Points()
+    points.load_from(extra_params["points"])
+
+    newpoints = []
+    for pair in points.list:
+        x = pair[1][0] + 0.5
+        y = pair[1][1] + 0.5
+        z = pair[1][2] + 0.5
+        newpoints.append((pair[0], (x,y,z)))
+    return [w3d_int.SetPoints(newpoints)]
+
+w3d_main_function = main
+
+ +The op variable contains which mode the command script is in, such as "vertex", "edge", "face" and "body". The modes the script runs in is set with the mode directive in the .wscr file. + +An .wscr file to use with the script: + +
+type "py"
+name ?__(1,"Example Simple Script")
+desc ?__(2,"Example Simple Script")
+mode "body"
+params_title ?__(3,"Example Simple Script")
+command_inputs "points"
+command_changes "points"
+params {
+  param "Unused" 0.0
+}
+
+ +The .wscr and .scm file should have the same name before the extension (script.scm and script.wscr). + +To use parameters with the command script, such as shifting the points by an adjustable value: + +
+import w3d_int
+
+def command(params, params_by_key, extra_params):
+    op = extra_params["op"]
+    val = params[0]
+    points = w3d_int.Points()
+    points.load_from(extra_params["points"])
+
+    newpoints = []
+    for pair in points.list:
+        x = pair[1][0] + val
+        y = pair[1][1] + val
+        z = pair[1][2] + val
+        newpoints.append((pair[0], (x,y,z)))
+    return [w3d_int.SetPoints(newpoints)]
+
+w3d_main_function = main
+
+ +In the .wscr file, rename the "Unused" parameter to "Move": + +
+type "py"
+name ?__(1,"Example Simple Script")
+desc ?__(2,"Example Simple Script")
+mode "body"
+params_title ?__(3,"Example Simple Script")
+command_inputs "points"
+command_changes "points"
+params {
+  param "Move" 0.5
+}
+
+ + + +Changes + +Depending on the kind of changes you are making, you add to the list: +SetPoints +SetFaceUVs +SetFaceColors +SetE3dMesh + +You also need to add to the command_inputs and command_changes directives the data needed, and what data will be changed. For the script using set_points, the following is needed in the .wscr file: +
+command_inputs "points"
+command_changes "points"
+
+ + +Advanced Commands + +Simple command scripts can be used to change the point coordinates, the UV values and colors. For more complicated tasks where the mesh needs more complicated changes, there are two options: E3DMesh scripts and We function based scripts. + +The E3DMesh script option the .wscr needs to specify the following options: +
+command_inputs "e3d_mesh"
+command_changes "e3d_mesh"
+
+ +With this option, the script gets a E3DMesh object, and the E3DMesh can be changed, and then returned. Then the object's winged edge structure is recreated. This option is most useful when the script is a wrapper for external tools that expect meshes as list of faces and vertex array such as in a .obj file. A caveat of this option is the mesh will have to be turned into a list of faces and vertices, and then rebuilt back to a winged edge structure, so faces, edges and vertices will be renumbered. + +An example command that just returns the E3DMesh unchanged: + +
+from w3d_e3d import E3DMesh, SetE3DMesh
+
+def command(params, params_by_key, extra_params):
+    op   = extra_params["op"]
+    mesh = E3DMesh()
+    mesh.load_from(extra_params["e3d_mesh"])
+
+    return [SetE3DMesh(mesh)]
+
+w3d_main_function = command
+
+ +An example of moving the object by 0.5 in all axis: + +
+from w3d_e3d import E3DMesh, SetE3DMesh
+
+def command(params, params_by_key, extra_params):
+    op   = extra_params["op"]
+    mesh = E3DMesh()
+    mesh.load_from(extra_params["e3d_mesh"])
+
+    points = mesh.vs
+
+    newpoints = []
+    for (x,y,z) in points:
+        newpoints.append((x + 0.5, y + 0.5, z + 0.5))
+
+    mesh.vs = newpoints
+
+    return [SetE3DMesh(mesh)]
+
+w3d_main_function = command
+
+ +An .wscr file to use with the script: + +
+type "py"
+name ?__(1,"Example E3D Mesh Script")
+desc ?__(2,"Example E3D Mesh Script")
+mode "body"
+params_title ?__(3,"Example E3D Mesh Script")
+command_inputs "e3d_mesh"
+command_changes "e3d_mesh"
+params {
+  param "Unused" 0.0
+}
+
+ + +Advanced We Function Scripts + +Scripts can use the we functions from Wings3D directly. When using this option, the command script does not need to use the command_inputs and command_changes directives, and the return from the script can be an empty list. Instead the script contains a sequence of we function calls that modifies the we object. + + +An example of moving the object by 0.5 in all axis using a we function, in this case, matrix transformation of the vertices: + +
+import w3d_we
+
+def command(params, params_by_key, extra_params):
+    op   = extra_params["op"]
+
+    move_x = 0.5
+    move_y = 0.5
+    move_z = 0.5
+    w3d_we.we__transform_vs([
+        1.0, 0.0, 0.0,
+        0.0, 1.0, 0.0,
+        0.0, 0.0, 1.0,
+        move_x, move_y, move_z])
+
+    return []
+
+w3d_main_function = command
+
+ +A list of all the available callable we functions is available in the w3d_we function references. + +An .wscr file to use for the we function command script: + +
+type "py"
+name ?__(1,"Example We Script")
+desc "Example We Script"
+mode "body"
+params_title ?__(2,"Example We Script")
+params_preview 1
+params {
+  param "Unused" 0.0
+}
+
+ + + diff --git a/plugins_src/scripting/docs/en/python/exporter_scripts.txt b/plugins_src/scripting/docs/en/python/exporter_scripts.txt new file mode 100644 index 000000000..0185b1092 --- /dev/null +++ b/plugins_src/scripting/docs/en/python/exporter_scripts.txt @@ -0,0 +1,105 @@ +Exporter Scripts + +Exporter scripts are available from the "File" > "Export" and "File" > "Export Selected" menus. + +Read E3DFile + +The "filename" and "content" extra parameters are needed, the file name is chosen by the user to save the model, and the content contains an e3d_file data structure. + +
+def main(params, params_by_key, extra_params):
+    filename = extra_params["filename"]
+    content = w3d_e3d.E3DFile()
+    content.load_from(extra_params["content"])
+
+    ##
+    ## ...
+    ##
+
+    ## Return Okay() if the file was written successfully.
+    return Okay()
+
+w3d_main_function = main
+
+ +Don't forget to assign the 'main' function to 'w3d_main_function'. + +Exporters take in both a filename and the e3d_file content which has to be loaded with E3DFile.load_from(...). After writing the file to disk, create an "ok" tuple and write its output list out: + +
+    return Okay()
+
+ + +A minimal exporter that writes vertices to a file: + +
+import w3d_e3d
+from os import path
+
+def export_fun(attr, filename, content):
+    objs = content.objs
+    with open(filename, "w", encoding="utf-8") as fp:
+        # Number of objects
+        fp.write(str(len(objs)) + "\n")
+        for obj in objs:
+            mesh = obj.obj
+            vs = mesh.vs
+            faces = mesh.fs
+            # Object name
+            fp.write(str(obj.name) + "\n")
+            # Number of vertices
+            fp.write(str(len(vs)) + "\n")
+            for v in vs:
+                x = v[0]
+                y = v[1]
+                z = v[2]
+                # Vertice coordinate
+                fp.write(str(x) + " " + str(y) + " " + str(z) + "\n")
+            # Number of faces
+            fp.write(str(len(faces)) + "\n")
+            for face in faces:
+                vlist = face.vs
+                for v in vlist:
+                    fp.write(str(v) + " ")
+                fp.write("\n")
+        
+        return Okay()
+
+def exporter(params, params_by_key, extra_params):
+    filename = extra_params["filename"]
+    content = w3d_e3d.E3DFile()
+    content.load_from(extra_params["content"])
+    return export_fun({}, filename, content)
+
+w3d_main_function = exporter
+
+ + +A .wscr file for the exporter: + +
+type "py"
+name ?__(1,"Example Exporter (.txt)")
+mode "export"
+params_title ?__(3,"Example Exporter Settings")
+params_templates {
+  template export include_normals,include_uvs,include_colors
+}
+params_set {
+  export_param include_uvs  %[bool(params/include_uvs)]
+  export_param subdivisions %[params/subdivisions]
+  export_param include_normals %[bool(params/include_normals)]
+  export_param include_hard_edges 'true'
+  export_param script_texture_convert 'user'
+}
+extensions {
+  ext .txt ?__(5,"Text Mesh")
+}
+
+ + +The .wscr and .scm file should have the same name before the extension (script.scm and script.wscr). + + + diff --git a/plugins_src/scripting/docs/en/python/functions/e3d_face.txt b/plugins_src/scripting/docs/en/python/functions/e3d_face.txt new file mode 100644 index 000000000..a7b350884 --- /dev/null +++ b/plugins_src/scripting/docs/en/python/functions/e3d_face.txt @@ -0,0 +1,17 @@ +class E3DFace + +Visible attributes vs, vc, tx, ns, mat, sg, vis + +E3DFace() + +Creates a new E3DFace instance + +face.as_output_list() + +Outputs the whole E3DFace instance and all its values into an output list, not usually used directly. + +face.load_from(expr) + +Load contents into a E3DFace instance, not usually used directly. + + diff --git a/plugins_src/scripting/docs/en/python/functions/e3d_file.txt b/plugins_src/scripting/docs/en/python/functions/e3d_file.txt new file mode 100644 index 000000000..539e7360d --- /dev/null +++ b/plugins_src/scripting/docs/en/python/functions/e3d_file.txt @@ -0,0 +1,31 @@ +class E3DFile + +Visible attributes objs, mat, creator, dir + +E3DFile() + +Creates a new E3DFile instance + +e3df.as_output_list() + +Outputs the whole E3DFile instance and all its values into an output list which is then to be written to stdout + +
+b = w3d_e3d.E3DFile()
+b.objs.append(E3DObject())
+o_ok = OutputList()
+o_ok.add_symbol("ok")
+o_ok.add_list(b.as_output_list())
+o_ok.write_list_out(sys.stdout)
+
+ +e3df.load_from(expr) + +Load contents into a E3DFile instance + +
+content = w3d_e3d.E3DFile()
+content.load_from(extra_params["content"])
+
+ + diff --git a/plugins_src/scripting/docs/en/python/functions/e3d_image.txt b/plugins_src/scripting/docs/en/python/functions/e3d_image.txt new file mode 100644 index 000000000..38e573c89 --- /dev/null +++ b/plugins_src/scripting/docs/en/python/functions/e3d_image.txt @@ -0,0 +1,17 @@ +class E3DImage + +Visible attributes type, bytes_pp, alignment, order, width, height, image, filename, name, extra + +E3DImage() + +Creates a new E3DImage instance + +image.as_output_list() + +Outputs the whole E3DImage instance and all its values into an output list, not usually used directly. + +image.load_from(expr) + +Load contents into a E3DImage instance, not usually used directly. + + diff --git a/plugins_src/scripting/docs/en/python/functions/e3d_mesh.txt b/plugins_src/scripting/docs/en/python/functions/e3d_mesh.txt new file mode 100644 index 000000000..c64f8784a --- /dev/null +++ b/plugins_src/scripting/docs/en/python/functions/e3d_mesh.txt @@ -0,0 +1,17 @@ +class E3DMesh + +Visible attributes: type, vs, vc, tx, ns, fs, he, matrix + +E3DMesh() + +Creates a new E3DMesh instance + +mesh.as_output_list() + +Outputs the whole E3DMesh instance and all its values into an output list, not usually used directly. + +mesh.load_from(expr) + +Load contents into a E3DMesh instance, not usually used directly. + + diff --git a/plugins_src/scripting/docs/en/python/functions/e3d_object.txt b/plugins_src/scripting/docs/en/python/functions/e3d_object.txt new file mode 100644 index 000000000..9f9cf170f --- /dev/null +++ b/plugins_src/scripting/docs/en/python/functions/e3d_object.txt @@ -0,0 +1,17 @@ +class E3DObject + +Visible attributes name, obj, mat, attr + +E3DObject() + +Creates a new E3DObject instance + +obj.as_output_list() + +Outputs the whole E3DObject instance and all its values into an output list, not usually used directly. + +obj.load_from(expr) + +Load contents into a E3DObject instance, not usually used directly. + + diff --git a/plugins_src/scripting/docs/en/python/functions/list_of_arrays.txt b/plugins_src/scripting/docs/en/python/functions/list_of_arrays.txt new file mode 100644 index 000000000..f2537c182 --- /dev/null +++ b/plugins_src/scripting/docs/en/python/functions/list_of_arrays.txt @@ -0,0 +1,13 @@ +class ListOfArrays + +Visible attributes: l + +ListOfArrays() + +Create a new empty ListOfArrays instance + +list.as_output_list() + +Return an OutputList instance + + diff --git a/plugins_src/scripting/docs/en/python/functions/list_of_tuples.txt b/plugins_src/scripting/docs/en/python/functions/list_of_tuples.txt new file mode 100644 index 000000000..ea9bb4848 --- /dev/null +++ b/plugins_src/scripting/docs/en/python/functions/list_of_tuples.txt @@ -0,0 +1,14 @@ +class ListOfTuples + +Visible attributes: l + +ListOfTuples() + +Create a new empty ListOfTuples instance + +list.as_output_list() + +Return an OutputList instance + + + diff --git a/plugins_src/scripting/docs/en/python/functions/material.txt b/plugins_src/scripting/docs/en/python/functions/material.txt new file mode 100644 index 000000000..fcbd2b033 --- /dev/null +++ b/plugins_src/scripting/docs/en/python/functions/material.txt @@ -0,0 +1,18 @@ +class Material + +Visible attributes name, attrs + +Material() + +Creates a new Material instance + +mat.as_output_list() + +Outputs the whole Material instance and all its values into an output list, + not usually used directly. + +mat.load_from(expr) + +Load contents into a Material instance, not usually used directly. + + diff --git a/plugins_src/scripting/docs/en/python/functions/material_maps.txt b/plugins_src/scripting/docs/en/python/functions/material_maps.txt new file mode 100644 index 000000000..8ca61d150 --- /dev/null +++ b/plugins_src/scripting/docs/en/python/functions/material_maps.txt @@ -0,0 +1,17 @@ +class MaterialMaps + +Visible attributes maps + +MaterialMaps() + +Create a new MaterialMaps instance + +maps.as_output_list() + +Outputs the whole MaterialMaps instance and all its values into an output list, not usually used directly. + +maps.load_from(tlist) + +Load contents into a MaterialMaps instance, not usually used directly. + + diff --git a/plugins_src/scripting/docs/en/python/functions/material_opengl.txt b/plugins_src/scripting/docs/en/python/functions/material_opengl.txt new file mode 100644 index 000000000..b5e3d6d35 --- /dev/null +++ b/plugins_src/scripting/docs/en/python/functions/material_opengl.txt @@ -0,0 +1,17 @@ +class MaterialOpenGLAttributes + +Visible attributes ambient, specular, shininess, diffuse, emission, metallic, roughness, vertex_colors + +MaterialOpenGLAttributes() + +Creates a new MaterialOpenGLAttributes instance + +opengl.as_output_list() + +Outputs the whole MaterialOpenGLAttributes instance and all its values into an output list, not usually used directly. + +opengl.load_from(tlist) + +Load contents into a MaterialOpenGLAttributes instance, not usually used directly. + + diff --git a/plugins_src/scripting/docs/en/python/functions/new_shape.txt b/plugins_src/scripting/docs/en/python/functions/new_shape.txt new file mode 100644 index 000000000..639d7ba02 --- /dev/null +++ b/plugins_src/scripting/docs/en/python/functions/new_shape.txt @@ -0,0 +1,22 @@ +class NewShape + +Visible attributes prefix, fs, vs, obj, mat + +NewShape() + +Create a NewShape instance + +shape.as_output_list() + +Return an OutputList instance, the kind of tuple that is returned is different depending on if obj is set, if it is set the tuple contains obj and mat. If it is not set, it returns a tuple with fs and vs. + +
+e3d_o = w3d_e3d.E3DObject()
+e3d_o.obj = mesh
+shp = w3d_newshape.NewShape()
+shp.prefix = "shape"
+shp.obj = e3d_o
+o = shp.as_output_list()
+o.write_list_out(sys.stdout)
+
+ diff --git a/plugins_src/scripting/docs/en/python/functions/output_list.txt b/plugins_src/scripting/docs/en/python/functions/output_list.txt new file mode 100644 index 000000000..e33bdd1b6 --- /dev/null +++ b/plugins_src/scripting/docs/en/python/functions/output_list.txt @@ -0,0 +1,60 @@ +class OutputList + +Visible attributes lst_cont, lst_type + +Python is somewhat different from Erlang and Scheme for their data structures, so to serialize objects for input and output with the script plugin, a helper class called OutputList is used. Other helper classes also use OutputList to be able to load and write data to and from Wings3D. + + +OutputList() + +Create a new empty OutputList instance + +list.add_symbol(a) + +Add a symbol (interned string) to the list + +list.add_str(a) + +Add a string to the list + +list.add_number(a) + +Add a number to the list + +list.add_numbers(alst) + +Add several numbers to the list, it does not add a sublist + +list.add_integer(a) + +Add an integer to the list + +list.add_integers(alst) + +Add several integers to the list, it does not add a sublist + +list.add_float(a) + +Add a floating point number to the list + +list.add_floats(alst) + +Add several floating point numbers to the list, it does not add a sublist + +list.add_list(a) + +Add a sublist to the list + +list.add_vector(a) + +Add a vector (tuple) to the list + +list.add(a, typ) + +Add an item with type number to the list, this method shouldn't be used directly. + +list.write_list_out(ost) + +Write the list to the stream ost + + diff --git a/plugins_src/scripting/docs/en/python/importer_scripts.txt b/plugins_src/scripting/docs/en/python/importer_scripts.txt new file mode 100644 index 000000000..5890f3572 --- /dev/null +++ b/plugins_src/scripting/docs/en/python/importer_scripts.txt @@ -0,0 +1,120 @@ +Importer Scripts + +Importer scripts are available from the "File" > "Import" menu. + +The "filename" extra parameter is needed which contain the file path of the file to read: + +
+def main(params, params_by_key, extra_params):
+    filename = extra_params["filename"]
+
+    ##
+    ## ...
+    ##
+
+    e3df = E3DFile()
+
+    ## After reading the file, return e3df
+    return Okay(ed3f)
+
+w3d_main_function = main
+
+ +Don't forget to assign the 'main' function to 'w3d_main_function'. + +Importers take the filename to read the file needed, construct a E3DObject, add it to an E3DFile and return the output list as part of an "ok" tuple. + +Constructing E3D objects involves instancing E3DFace, E3DObject, E3DFile, etc. + +
+    e3df = w3d_e3d.E3DFile()
+    e3df.objs = [obj] # obj is an E3DObject
+    return Okay(e3df)
+
+ + +A minimal importer that reads vertices to a file: + +
+import w3d_e3d
+from os import path
+
+def import_fun(attr, filename):
+    with open(filename, "r", encoding="utf-8") as fp:
+        # Number of objects
+        num_objs = int(fp.readline().rstrip())
+        objs = []
+        # Read each object
+        for i in range(0, num_objs):
+            # Object name
+            objname = fp.readline().rstrip()
+            # Number of vertices
+            num_vs = int(fp.readline().rstrip())
+            vs = []
+            for i2 in range(0, num_vs):
+                v = fp.readline().rstrip().split(" ")
+                vs.append((float(v[0]),float(v[1]),float(v[2])))
+            # Number of faces
+            num_faces = int(fp.readline().rstrip())
+            faces = []
+            for i2 in range(0, num_faces):
+                # Vertex indices
+                vf0 = fp.readline().rstrip().split(" ")
+                vf = []
+                for v in vf0:
+                    vf.append(int(v))
+                face = w3d_e3d.E3DFace()
+                face.vs = vf
+                face.vc = []
+                face.tx = []
+                face.ns = []
+                faces.append(face)
+            
+            mesh = w3d_e3d.E3DMesh()
+            mesh.type="polygon"
+            mesh.vs=vs
+            mesh.fs=faces
+            mesh.vc=[]
+            mesh.tx=[]
+            mesh.ns=[]
+            mesh.he=[]
+            obj = w3d_e3d.E3DObject()
+            obj.name = objname
+            obj.obj = mesh
+            objs.append(obj)
+        
+        e3df = w3d_e3d.E3DFile()
+        e3df.objs = objs
+        return Okay(e3df)
+
+def importer(params, params_by_key, extra_params):
+    filename = extra_params["filename"]
+    return import_fun({}, filename)
+
+w3d_main_function = importer
+
+ + +A .wscr file for the importer: + +
+type "py"
+name ?__(1,"Example Importer (.txt)")
+mode "import"
+params_title ?__(3,"Example Importer Settings")
+params_templates {
+  template import
+}
+params_set {
+  import_param script_texture_convert 'auto'
+}
+extensions {
+  ext .txt ?__(5,"Text Mesh")
+}
+
+ + +The .wscr and .scm file should have the same name before the extension (script.scm and script.wscr). + + + diff --git a/plugins_src/scripting/docs/en/python/intro.txt b/plugins_src/scripting/docs/en/python/intro.txt new file mode 100644 index 000000000..f5612bcbf --- /dev/null +++ b/plugins_src/scripting/docs/en/python/intro.txt @@ -0,0 +1,99 @@ +Introduction + +

.wscr File

+ +For your script to be able to appear in the script chooser dialog, you will need to make a wscr file with the same base name as your main python script file. For example, if your script file is example.py, your wscr file should be named example.wscr. The name that appears in the script chooser is the name specified in the wscr file. + +

Initial Bootstrap Script

+ +A bootstrap script that comes with the script plugin is first launched which then defines some basic functions, sometimes adds include paths, and gets from stdin the actual script being invoked as well as parameters that goes with it. + +

Parameters that are passed in

+ +When a script is invoked, parameters from the dialog box are passed in, other parameters are also passed in depending on the type of script. + +

Returning values from the script

+ +Scripts write to stdout an OutputList before exiting. + +

Calling functions in Wings3D from a script

+ +Scripts can communicate with the plugin by setting temporary variables and using queries to call functions and get the return value. Temporary variables makes it easier to input elaborate data structures as arguments by referencing them in the query. + + +Set value to variable: + +
+    w3d_int.wings_set_var("var1", [1, 2, 3, 4])
+
+ +The return value should be
["ok"]
+ + +Get value of variable: + +
+    w3d_int.wings_get_var("var1")
+
+ +The reply should be
[[1, 2, 3, 4]]
+ + +Query: + +
+    w3d_int.wings_query("lists:reverse(<$'var1')")
+
+ +The reply should be
[[4, 3, 2, 1]]
+ + +

Calling We Functions

+ +Most We functions are already wrapped into a defined function, and are covered in the we function reference. + +Other functions can be called with + wings_we__get(module,fun,*args) + wings_we__change(module,fun,*args) + + + +

NewShape (w3d_newshape) Functions

+ +The w3d_newshape module contains NewShape helper classes and can be imported with: + +
import w3d_newshape
+ +

E3D (w3d_e3d) Functions

+ +The w3d_e3d module contains E3D helper classes and can be imported with: + +
import w3d_e3d
+ +

We (w3d_we) Functions

+ +The w3d_we module contains Winged Edge functions for command scripts: + +
import w3d_we
+ + + +

Long Running Processes

+ +If your script runs for a long time, you want to use the LongRunningProcess class. The LongRunningProcess class continues your script processing in another thread, while sending back keep-alive messages to Wings3D so the program knows that the script has not timed out. + +
+class Examp:
+    def run(self):
+        sum = 0
+        for i in range(0, 100000000):
+            sum = sum + i
+
+print("Begin")
+lrp = LongRunningProcess()
+lrp.run(Examp())
+print("Done")
+
+ + + diff --git a/plugins_src/scripting/docs/en/python/langres.txt b/plugins_src/scripting/docs/en/python/langres.txt new file mode 100644 index 000000000..cf57aa075 --- /dev/null +++ b/plugins_src/scripting/docs/en/python/langres.txt @@ -0,0 +1,13 @@ +Online Resources for the Python language + +A non-exhaustive list of resources: + +Python Tutorial +https://docs.python.org/3/tutorial/ + +Python Language Reference +https://docs.python.org/3/reference/ + + + + diff --git a/plugins_src/scripting/docs/en/python/new_shape_scripts.txt b/plugins_src/scripting/docs/en/python/new_shape_scripts.txt new file mode 100644 index 000000000..c3e3f8896 --- /dev/null +++ b/plugins_src/scripting/docs/en/python/new_shape_scripts.txt @@ -0,0 +1,123 @@ +New Shape Scripts + +New shape scripts are available when right clicking with no selection. + +A complete example of a Python script to create a new shape: + +
+import w3d_newshape
+import sys
+
+def main(params, params_by_key, extra_params):
+    nshp = w3d_newshape.NewShape()
+    nshp.fs = w3d_newshape.ListOfArrays([
+        [0,1,3,2],
+        [6,7,5,4],
+        [1,0,4,5],
+        [2,3,7,6],
+        [0,2,6,4],
+        [3,1,5,7]
+    ])
+    nshp.vs = w3d_newshape.ListOfTuples([
+        (-2.0, 0.5,-2.0),
+        (-2.0, 0.5, 2.0),
+        ( 2.0, 0.5,-2.0),
+        ( 2.0, 0.5, 2.0),
+        (-2.0,-0.5,-2.0),
+        (-2.0,-0.5, 2.0),
+        ( 2.0,-0.5,-2.0),
+        ( 2.0,-0.5, 2.0)
+    ])
+    return nshp
+
+w3d_main_function = main
+
+ +Don't forget to assign the 'main' function to 'w3d_main_function'. + +An .wscr to use with the script: + +
+type "py"
+name ?__(1,"Example New Shape")
+params_title ?__(3,"Example New Shape")
+params {
+param "Unused" 0.0
+}
+
+ +The .wscr and .py file should have the same name before the extension (script.py and script.wscr). + +To use parameters with the script, such as shifting the points by an adjustable value: + +
+import w3d_newshape
+import sys
+
+def main(params, params_by_key, extra_params):
+    xshift = params[0]
+    yshift = params[1]
+    zshift = params[2]
+    nshp = w3d_newshape.NewShape()
+    nshp.fs = w3d_newshape.ListOfArrays([
+        [0,1,3,2],
+        [6,7,5,4],
+        [1,0,4,5],
+        [2,3,7,6],
+        [0,2,6,4],
+        [3,1,5,7]
+    ])
+    nshp.vs = w3d_newshape.ListOfTuples([
+        (-2.0 + xshift,  0.5 + yshift, -2.0 + zshift),
+        (-2.0 + xshift,  0.5 + yshift,  2.0 + zshift),
+        ( 2.0 + xshift,  0.5 + yshift, -2.0 + zshift),
+        ( 2.0 + xshift,  0.5 + yshift,  2.0 + zshift),
+        (-2.0 + xshift, -0.5 + yshift, -2.0 + zshift),
+        (-2.0 + xshift, -0.5 + yshift,  2.0 + zshift),
+        ( 2.0 + xshift, -0.5 + yshift, -2.0 + zshift),
+        ( 2.0 + xshift, -0.5 + yshift,  2.0 + zshift)
+    ])
+    return nshp
+
+w3d_main_function = main
+
+ +In the .wscr file, replace the "Unused" parameter with "X", "Y" and "Z" parameters: + +
+type "py"
+name ?__(1,"Example New Shape")
+params_title ?__(3,"Example New Shape")
+params {
+param "X" 0.0
+param "Y" 0.0
+param "Z" 0.0
+}
+
+ + + +Another way to create a new shape where you might want to add colors, UVs, materials and hard edges is to make a new shape using a E3DObject. To create a shape, create a geometry with E3DMesh and E3DObject, and assign E3DObject to the obj attribute of a NewShape object. + +
+import w3d_newshape
+import w3d_e3d
+import sys
+
+def main(params, params_by_key, extra_params):
+    mesh = w3d_e3d.E3DMesh()
+    ...
+    e3d_o = w3d_e3d.E3DObject()
+    e3d_o.obj = mesh
+    nshp = w3d_newshape.NewShape()
+    nshp.prefix = "shape"
+    nshp.obj = e3d_o
+    return nshp
+
+w3d_main_function = main
+
+ + + + + diff --git a/plugins_src/scripting/docs/en/scheme/command_scripts.txt b/plugins_src/scripting/docs/en/scheme/command_scripts.txt new file mode 100644 index 000000000..177756206 --- /dev/null +++ b/plugins_src/scripting/docs/en/scheme/command_scripts.txt @@ -0,0 +1,247 @@ +Command Scripts + +Command scripts are available when right clicking on a selected object + +The "points" extra parameter is needed to access the list of vertices, which come as a vertice number and a 3 dimensional position. + +
+(main-function
+    (lambda (*params* *extra-params*)
+        ;; The mode of the command: 'body 'face 'vertex 'edge
+        (define op     (list-ref
+                         (assoc "op" *extra-params*) 1))
+        ;; The list of selected items (if op is 'face , then sel
+        ;; contains a list of face numbers.
+        (define sel    (list-ref
+                         (assoc "sel" *extra-params*) 1))
+        ;; A "points" list is available if the command_inputs
+        ;; has "points" in its comma-separated list.
+        (define points (list-ref
+                         (assoc "points" *extra-params*) 1))
+
+        ;; ...
+
+        ;; Return new positions of vertices
+        (list (list 'set_points points))
+        ))
+
+ + +The following script shifts all the vertice positions of a selected object by 0.5 in all axis: + +
+(main-function
+    (lambda (*params* *extra-params*)
+        (define op     (list-ref
+                         (assoc "op"     *extra-params*) 1))
+        (define points (list-ref
+                         (assoc "points" *extra-params*) 1))
+        (define newpoints
+            (map (lambda (p)
+                (define a (vector-ref p 0))
+                (define b (vector-ref p 1))
+                (define x (vector-ref b 0))
+                (define y (vector-ref b 1))
+                (define z (vector-ref b 2))
+                (vector a (vector
+                            (+ 0.5 x)
+                            (+ 0.5 y)
+                            (+ 0.5 z)))
+            ) points))
+        (list (list 'set_points newpoints ))
+        ))
+
+ +The op variable contains which mode the command script is in, such as "vertex", "edge", "face" and "body". The modes the script runs in is set with the mode directive in the .wscr file. + +An .wscr file to use with the script: + +
+type "scm"
+name ?__(1,"Example Simple Script")
+desc ?__(2,"Example Simple Script")
+mode "body"
+params_title ?__(3,"Example Simple Script")
+command_inputs "points"
+command_changes "points"
+params {
+  param "Unused" 0.0
+}
+
+ +The .wscr and .scm file should have the same name before the extension (script.scm and script.wscr). + +To use parameters with the command script, such as shifting the points by an adjustable value: + +
+(main-function
+    (lambda (*params* *extra-params*)
+        (define op     (list-ref
+                         (assoc "op"     *extra-params*) 1))
+        (define points (list-ref
+                         (assoc "points" *extra-params*) 1))
+        (define val (list-ref *params* 0))
+        (define newpoints
+            (map (lambda (p)
+                (define a (vector-ref p 0))
+                (define b (vector-ref p 1))
+                (define x (vector-ref b 0))
+                (define y (vector-ref b 1))
+                (define z (vector-ref b 2))
+                (vector a (vector
+                            (+ val x)
+                            (+ val y)
+                            (+ val z)))
+            ) points))
+        (list (list 'set_points newpoints ))
+        ))
+
+ +In the .wscr file, rename the "Unused" parameter to "Move": + +
+type "scm"
+name ?__(1,"Example Simple Script")
+desc ?__(2,"Example Simple Script")
+mode "body"
+params_title ?__(3,"Example Simple Script")
+command_inputs "points"
+command_changes "points"
+params {
+  param "Move" 0.5
+}
+
+ + + +Changes + +Depending on the kind of changes you are making, you add to the list: +'set_points +'set_face_uvs +'set_face_colors +'set_e3d_mesh + +You also need to add to the command_inputs and command_changes directives the data needed, and what data will be changed. For the script using set_points, the following is needed in the .wscr file: +
+command_inputs "points"
+command_changes "points"
+
+ + +Advanced Commands + +Simple command scripts can be used to change the point coordinates, the UV values and colors. For more complicated tasks where the mesh needs more complicated changes, there are two options: E3DMesh scripts and We function based scripts. + +The E3DMesh script option the .wscr needs to specify the following options: +
+command_inputs "e3d_mesh"
+command_changes "e3d_mesh"
+
+ +With this option, the script gets a E3DMesh object, and the E3DMesh can be changed, and then returned. Then the object's winged edge structure is recreated. This option is most useful when the script is a wrapper for external tools that expect meshes as list of faces and vertex array such as in a .obj file. A caveat of this option is the mesh will have to be turned into a list of faces and vertices, and then rebuilt back to a winged edge structure, so faces, edges and vertices will be renumbered. + +An example command that just returns the E3DMesh unchanged: + +
+(main-function command)
+    (lambda (*params* *extra-params*)
+        (define op   (list-ref (assoc "op" *extra-params*) 1))
+        (define mesh (list-ref (assoc "e3d_mesh" *extra-params*) 1))
+
+        (list (list 'set_e3d_mesh mesh))
+        ))
+
+ +An example of moving the object by 0.5 in all axis: + +
+(main-function command)
+    (lambda (*params* *extra-params*)
+        (define op   (list-ref (assoc "op" *extra-params*) 1))
+        (define mesh (list-ref (assoc "e3d_mesh" *extra-params*) 1))
+
+        (define points (e3d_mesh-vs mesh))
+
+        (define newpoints #f)
+        (define newmesh #f)
+
+        (set! newpoints
+            (map (lambda (b)
+                (let ((x (vector-ref b 0))
+                      (y (vector-ref b 1))
+                      (z (vector-ref b 2)))
+                    (vector
+                        (+ 0.5 x)
+                        (+ 0.5 y)
+                        (+ 0.5 z)))
+            ) points))
+
+        (set! newmesh
+            (**loader-construct-from `((vs ,newpoints)) mesh (list 'type  'vs  'vc  'tx  'ns  'fs 'he  'matrix))
+            )
+        (list (list 'set_e3d_mesh newmesh))
+        ))
+
+ +An .wscr file to use with the script: + +
+type "scm"
+name ?__(1,"Example E3D Mesh Script")
+desc ?__(2,"Example E3D Mesh Script")
+mode "body"
+params_title ?__(3,"Example E3D Mesh Script")
+command_inputs "e3d_mesh"
+command_changes "e3d_mesh"
+params {
+  param "Unused" 0.0
+}
+
+ + +Advanced We Function Scripts + +Scripts can use the we functions from Wings3D directly. When using this option, the command script does not need to use the command_inputs and command_changes directives, and the return from the script can be an empty list. Instead the script contains a sequence of we function calls that modifies the we object. + + +An example of moving the object by 0.5 in all axis using a we function, in this case, matrix transformation of the vertices: + +
+(main-function command)
+    (lambda (*params* *extra-params*)
+        (define op     (list-ref (assoc "op" *extra-params*) 1))
+        
+        (define MoveX 0.5)
+        (define MoveY 0.5)
+        (define MoveZ 0.5)
+        
+        (we:we:transform_vs!
+            (list 1.0 0.0 0.0
+                  0.0 1.0 0.0
+                  0.0 0.0 1.0
+                  MoveX MoveY MoveZ))
+
+        (list)
+        ))
+
+ +A list of all the available callable we functions is available in the w3d_we function references. + +An .wscr file to use for the we function command script: + +
+type "scm"
+name ?__(1,"Example We Script")
+desc "Example We Script"
+mode "body"
+params_title ?__(2,"Example We Script")
+params_preview 1
+params {
+  param "Unused" 0.0
+}
+
+ + + + diff --git a/plugins_src/scripting/docs/en/scheme/exporter_scripts.txt b/plugins_src/scripting/docs/en/scheme/exporter_scripts.txt new file mode 100644 index 000000000..163211c75 --- /dev/null +++ b/plugins_src/scripting/docs/en/scheme/exporter_scripts.txt @@ -0,0 +1,120 @@ +Exporter Scripts + +Exporter scripts are available from the "File" > "Export" and "File" > "Export Selected" menus. + +Read e3d_file + +The "filename" and "content" extra parameters are needed, the file name is chosen by the user to save the model, and the content contains an e3d_file data structure. + +
+(main-function
+    (lambda (*params* *extra-params*)
+        ;; The filename to write to
+        (define filename
+          (list-ref
+            (assoc "filename" *extra-params*) 1))
+        ;; The e3d_file contents
+        (define content
+          (list-ref
+            (assoc "content"  *extra-params*) 1))
+
+        ;;
+        ;; ...
+        ;;
+
+        ;; Return '(ok) after successfully writing the file
+        '(ok)
+        ))
+
+ +After writing the content to a file, the exporter returns a list with only 'ok' to Wings3D: + +
'(ok)
+ + +A minimal exporter that writes vertices to a file: + +
+(use srfi-1)
+(use srfi-13)
+
+(define (export_fun Attr Filename Contents)
+    (define Objs (e3d_file-objs Contents))
+    (call-with-output-file Filename (lambda (Fp)
+        ;; Number of objects
+        (display (format "~w\n" (length Objs)) Fp)
+        (for-each (lambda (Obj)
+            (define Mesh (e3d_object-obj Obj))
+            (define Name (e3d_object-name Obj))
+            (define Vs (e3d_mesh-vs Mesh))
+            (define Faces (e3d_mesh-fs Mesh))
+            ;; Name of object
+            (display (format "~a\n" Name) Fp)
+            ;; Number of vertices
+            (display (format "~w\n" (length Vs)) Fp)
+            (for-each (lambda (V)
+                (define X (vector-ref V 0))
+                (define Y (vector-ref V 1))
+                (define Z (vector-ref V 2))
+                ;; Vertex coordinates
+                (display (format "~f ~f ~f\n" X Y Z) Fp)
+                ) Vs)
+            ;; Number of faces
+            (display (format "~w\n" (length Faces)) Fp)
+            (for-each (lambda (Face)
+                (define VList (e3d_face-vs Face))
+                (display (format "DEBUG:~w\n" VList))
+                (for-each (lambda (V)
+                    ;; Index
+                    (display (format "~w " V) Fp)
+                    ) VList)
+                (display (format "\n") Fp)
+                ) Faces)
+            ) Objs)
+        '(ok)
+        ) :encoding 'utf8)
+    )
+
+(define (exporter *params* *extra-params*)
+    ;; e3d_file tuple is found in "content" of extra parameters, and the
+    ;; filename to export to is in filename.
+    ;;
+    (define Filename (list-ref (assoc "filename" *extra-params*) 1))
+    (define Content (list-ref (assoc "content" *extra-params*) 1))
+    (newline)
+    (export_fun '() Filename Content)
+    )
+
+(main-function exporter)
+
+ + +A .wscr file for the exporter: + +
+type "scm"
+name ?__(1,"Example Exporter (.txt)")
+mode "export"
+params_title ?__(3,"Example Exporter Settings")
+params_init {
+  do %[1>$'subdivisions']
+}
+params_templates {
+  template export include_normals,include_uvs,include_colors
+}
+params_set {
+  export_param include_uvs  %[bool(params/include_uvs)]
+  export_param subdivisions %[params/subdivisions]
+  export_param include_hard_edges 'true'
+  export_param script_texture_convert 'user'
+}
+extensions {
+  ext .txt ?__(5,"Text Mesh")
+}
+
+ + +The .wscr and .scm file should have the same name before the extension (script.scm and script.wscr). + + + diff --git a/plugins_src/scripting/docs/en/scheme/functions/e3d_face.txt b/plugins_src/scripting/docs/en/scheme/functions/e3d_face.txt new file mode 100644 index 000000000..a6c37c547 --- /dev/null +++ b/plugins_src/scripting/docs/en/scheme/functions/e3d_face.txt @@ -0,0 +1,37 @@ +e3d_face + +(make-e3d_face default + . + sc) + +Make a e3d_face list structure with a constructor list, available items are 'vs 'vc 'tx 'ns 'mat 'sg 'vis + +(e3d_face? + l) + +Is the list structure an e3d_face? + +(e3d_face-vs + l) + +(e3d_face-vc + l) + +(e3d_face-tx + l) + +(e3d_face-ns + l) + +(e3d_face-mat + l) + +(e3d_face-sg + l) + +(e3d_face-vis + l) + +Get the value of a given field in e3d_face. + + diff --git a/plugins_src/scripting/docs/en/scheme/functions/e3d_file.txt b/plugins_src/scripting/docs/en/scheme/functions/e3d_file.txt new file mode 100644 index 000000000..cec06c45e --- /dev/null +++ b/plugins_src/scripting/docs/en/scheme/functions/e3d_file.txt @@ -0,0 +1,28 @@ +e3d_file + +(make-e3d_file + . + sc) + +Make a e3d_file list structure with a constructor list, available items are 'objs 'mat 'creator 'dir + +(e3d_file? + l) + +Is the list structure an e3d_file? + +(e3d_file-objs + l) + +(e3d_file-mat + l) + +(e3d_file-creator + l) + +(e3d_file-dir + l) + +Get the value of a given field in e3d_file + + diff --git a/plugins_src/scripting/docs/en/scheme/functions/e3d_image.txt b/plugins_src/scripting/docs/en/scheme/functions/e3d_image.txt new file mode 100644 index 000000000..2e7a466b5 --- /dev/null +++ b/plugins_src/scripting/docs/en/scheme/functions/e3d_image.txt @@ -0,0 +1,48 @@ +e3d_image + +(make-e3d_image + . + sc) + +Make a e3d_image list structure with a constructor list, available items are 'type 'bytes_pp 'alignment 'order 'width 'height 'image 'filename 'name 'extra + +(e3d_image? + l) + +Is the list structure an e3d_image? + +(e3d_image-type + l) + +(e3d_image-bytes_pp + l) + +(e3d_image-alignment + l) + +(e3d_image-order + l) + +(e3d_image-width + l) + +(e3d_image-height + l) + +(e3d_image-image + l) + +(e3d_image-filename + l) + +(e3d_image-name + l) + +(e3d_image-extra + l) + +Get the value of a given field in e3d_image + + + + diff --git a/plugins_src/scripting/docs/en/scheme/functions/e3d_mesh.txt b/plugins_src/scripting/docs/en/scheme/functions/e3d_mesh.txt new file mode 100644 index 000000000..3c03b8518 --- /dev/null +++ b/plugins_src/scripting/docs/en/scheme/functions/e3d_mesh.txt @@ -0,0 +1,40 @@ +e3d_mesh + +(make-e3d_mesh + . + sc) + +Make a e3d_mesh list structure with a constructor list, available items are 'type 'vs 'vc 'tx 'ns 'fs 'he 'matrix + +(e3d_mesh? + l) + +Is the list structure an e3d_mesh? + +(e3d_mesh-type + l) + +(e3d_mesh-vs + l) + +(e3d_mesh-vc + l) + +(e3d_mesh-tx + l) + +(e3d_mesh-ns + l) + +(e3d_mesh-fs + l) + +(e3d_mesh-he + l) + +(e3d_mesh-matrix + l) + +Get the value of a given field in e3d_mesh + + diff --git a/plugins_src/scripting/docs/en/scheme/functions/e3d_object.txt b/plugins_src/scripting/docs/en/scheme/functions/e3d_object.txt new file mode 100644 index 000000000..4ec21d7b0 --- /dev/null +++ b/plugins_src/scripting/docs/en/scheme/functions/e3d_object.txt @@ -0,0 +1,29 @@ +e3d_object + +(make-e3d_object + . + sc) + +Make a e3d_object list structure with a constructor list, available items + are 'name 'obj 'mat 'attr + +(e3d_object? + l) + +Is the list structure an e3d_object? + +(e3d_object-name + l) + +(e3d_object-obj + l) + +(e3d_object-mat + l) + +(e3d_object-attr + l) + +Get the value of a given field in e3d_object + + diff --git a/plugins_src/scripting/docs/en/scheme/importer_scripts.txt b/plugins_src/scripting/docs/en/scheme/importer_scripts.txt new file mode 100644 index 000000000..8c1014941 --- /dev/null +++ b/plugins_src/scripting/docs/en/scheme/importer_scripts.txt @@ -0,0 +1,126 @@ +Importer Scripts + +Importer scripts are available from the "File" > "Import" menu. + +Importer scripts are scripts that open a file, and with the mesh information in the file, creates an e3d_file data structure and returns it. + +The "filename" extra parameter is needed which contain the file path of the file to read: + +
+(main-function
+    (lambda (*params* *extra-params*)
+        (define filename
+          (list-ref
+            (assoc "filename" *extra-params*) 1))
+
+        ;;
+        ;; ...
+        ;;
+
+        ;; Return an e3d_file in a variable called e3df
+        (list 'ok e3df)
+        ))
+
+ +Constructing e3d objects involves using +make-e3d_face, +make-e3d_object, + make-e3dfile, etc. + +After parsing the file, the importer returns the contents of e3d_file back to Wings3D: + +
(list 'ok (make-e3d_file `(objs ,(list Obj))))
+ + +A minimal importer that reads vertices from a file: + +
+(use srfi-1)
+(use srfi-13) ; For string-tokenize
+
+(define (read-for-each Fp F)
+    (define NumVals (string->number (read-line Fp))) ; Number of values
+    (let loop ((I 0) (Vals (list)))
+        (if (< I NumVals)
+            (let ((Ret (F)))
+                (loop (+ I 1) (cons Ret Vals))
+                )
+            (reverse Vals))
+        )
+    )
+
+(define (import_fun Attr Filename)
+    (call-with-input-file Filename (lambda (Fp)
+        ; Read each object
+        (define Objs (read-for-each Fp
+            (lambda ()
+                (define ObjName (read-line Fp))
+                ; Read each vertex
+                (define Vs (read-for-each Fp
+                    (lambda ()
+                        (define Vtx0 (string-tokenize (read-line Fp)))
+                        ; Vertex coordinate
+                        (define Vtx (map exact->inexact (map string->number Vtx0)))
+                        (list->vector Vtx))))
+                ; Read each face
+                (define Faces (read-for-each Fp
+                    (lambda ()
+                        ; Vertex indices
+                        (define VList0 (string-tokenize (read-line Fp)))
+                        (define VList (map string->number VList0))
+                        (make-e3d_face 
+                                `(vs ,VList)
+                                `(vc ,(list))
+                                `(tx ,(list))
+                                `(ns ,(list))
+                            )
+                        )))
+                (define Mesh (make-e3d_mesh
+                    `(type polygon)
+                    `(vs ,Vs)
+                    `(fs ,Faces)
+                    `(vc ,(list))
+                    `(tx ,(list))
+                    `(ns ,(list))
+                    `(he ,(list))
+                    ))
+                (make-e3d_object `(name ,ObjName) `(obj ,Mesh))
+                )))
+        (list 'ok (make-e3d_file `(objs ,Objs)))
+        ) :encoding 'utf8)
+    )
+
+(define (importer *params* *extra-params*)
+    ;; the filename to import from is in "filename" of extra parameters.
+    ;;
+    (define Filename (list-ref (assoc "filename" *extra-params*) 1))
+    (import_fun '() Filename)
+    )
+
+(main-function importer)
+
+ + +A .wscr file for the importer: + +
+type "scm"
+name ?__(1,"Example Importer (.txt)")
+mode "import"
+params_title ?__(3,"Example Importer Settings")
+params_templates {
+  template import
+}
+params_set {
+  import_param script_texture_convert 'auto'
+}
+extensions {
+  ext .txt ?__(5,"Text Mesh")
+}
+
+ + +The .wscr and .scm file should have the same name before the extension (script.scm and script.wscr). + + + diff --git a/plugins_src/scripting/docs/en/scheme/intro.txt b/plugins_src/scripting/docs/en/scheme/intro.txt new file mode 100644 index 000000000..d9258eb0a --- /dev/null +++ b/plugins_src/scripting/docs/en/scheme/intro.txt @@ -0,0 +1,83 @@ +Introduction + +

.wscr File

+ +For your script to be able to appear in the script chooser dialog, you will need to make a .wscr file with the same base name as your main Scheme script file. For example, if your script file is example.scm, your wscr file should be named example.wscr. The name that appears in the script chooser is the name specified in the .wscr file. + +

Initial Bootstrap Script

+ +A bootstrap script that comes with the script plugin is first launched which then defines some basic functions, sometimes adds include paths, and gets from stdin the actual script being invoked as well as parameters that goes with it. + +

Parameters that are passed in

+ +When a script is invoked, parameters from the dialog box are passed in, other parameters are also passed in depending on the type of script. + +

Returning values from the script

+ +Scripts write to stdout their response before exiting. + +
    (list 'ok '(new_shape ... ))
+ +

Calling functions in Wings3D from a script

+ +Scripts can communicate with the plugin by setting temporary variables and using queries to call functions and get the return value. Temporary variables makes it easier to input elaborate data structures as arguments by referencing them in the query. + + +Set value to variable: + +
+    (wings-set-variable! "var1" '(1 2 3 4))
+
+ +The return should be
'ok
. + + +Get value of variable: + +
+    (wings-get-variable "var1")
+
+ +The return would be
'(1 2 3 4)
. + + +Query: + +
+    (wings-query "lists:reverse(<$'var1')")
+
+ +The return would be
'(4 3 2 1)
. + + + +

Calling We functions

+ +Most We functions are already wrapped into a defined function, and are covered in the we function reference. + +Other functions can be called with + (wings-we! Module FunName . Args) + (wings-we Module FunName . Args) + + +

Long Running Processes

+ +If your script runs for a long time, you want to use the long-running-process function. The long-running-process function continues your script processing in another thread, while sending back keep-alive messages to Wings3D so the program knows that the script has not timed out. + +
+(display "Started")(newline)
+(long-running-process
+    (lambda ()
+        (let loop ((N 100000000))
+            (if (> N 0)
+                (loop (- N 1))
+                'ok))
+    ))
+(display "Done")(newline)
+
+ + + + + + diff --git a/plugins_src/scripting/docs/en/scheme/langres.txt b/plugins_src/scripting/docs/en/scheme/langres.txt new file mode 100644 index 000000000..2e837890b --- /dev/null +++ b/plugins_src/scripting/docs/en/scheme/langres.txt @@ -0,0 +1,15 @@ +Online Resources for the Scheme Language + +A very non exhaustive list of resources: + +Scheme Language Tutorials and Books +https://docs.scheme.org/ + +Gauche reference +http://practical-scheme.net/gauche/man/gauche-refe/index.html + +R5RS (reference for functions found in nearly every Scheme to write portable code) +https://conservatory.scheme.org/schemers/Documents/Standards/R5RS/ + + + diff --git a/plugins_src/scripting/docs/en/scheme/new_shape_scripts.txt b/plugins_src/scripting/docs/en/scheme/new_shape_scripts.txt new file mode 100644 index 000000000..e41d9fcbd --- /dev/null +++ b/plugins_src/scripting/docs/en/scheme/new_shape_scripts.txt @@ -0,0 +1,109 @@ +New Shape Scripts + +New shape scripts are available when right clicking with no selection. + +An example of a complete Scheme script to create a shape: + +
+(main-function
+    (lambda (*params* *extra-params*)
+        (list 'new_shape "MyShape"
+            '(
+                (0 1 3 2)
+                (6 7 5 4)
+                (1 0 4 5)
+                (2 3 7 6)
+                (0 2 6 4)
+                (3 1 5 7)
+            ) '(
+                #(-2.0 0.5 -2.0)
+                #(-2.0 0.5 2.0)
+                #(2.0 0.5 -2.0)
+                #(2.0 0.5 2.0)
+                #(-2.0 -0.5 -2.0)
+                #(-2.0 -0.5 2.0)
+                #(2.0 -0.5 -2.0)
+                #(2.0 -0.5 2.0)
+            )) 
+        ))
+
+ +An .wscr file to use with the script: + +

+type "scm"
+name ?__(1,"Example New Shape")
+params_title ?__(3,"Example New Shape")
+params {
+param "Unused" 0.0
+}
+
+ +The .wscr and .scm file should have the same name before the extension (script.scm and script.wscr). + +To use parameters with the script, such as shifting the points by an adjustable value: + +
+(main-function
+    (lambda (*params* *extra-params*)
+        (define xshift (list-ref *params* 0))
+        (define yshift (list-ref *params* 1))
+        (define zshift (list-ref *params* 2))
+        (list 'new_shape "MyShape"
+            '(
+                (0 1 3 2)
+                (6 7 5 4)
+                (1 0 4 5)
+                (2 3 7 6)
+                (0 2 6 4)
+                (3 1 5 7)
+            ) '(
+                (vector (+ val -2.0) (+ yshift 0.5) (+ zshift -2.0))
+                (vector (+ val -2.0) (+ yshift 0.5) (+ zshift 2.0))
+                (vector (+ val 2.0) (+ yshift 0.5) (+ zshift -2.0))
+                (vector (+ val 2.0) (+ yshift 0.5) (+ zshift 2.0))
+                (vector (+ val -2.0) (+ yshift -0.5) (+ zshift -2.0))
+                (vector (+ val -2.0) (+ yshift -0.5) (+ zshift 2.0))
+                (vector (+ val 2.0) (+ yshift -0.5) (+ zshift -2.0))
+                (vector (+ val 2.0) (+ yshift -0.5) (+ zshift 2.0))
+            )) 
+        ))
+
+ +In the .wscr file, replace the "Unused" parameter with "X", "Y" and "Z" parameters: + +

+type "scm"
+name ?__(1,"Example New Shape")
+params_title ?__(3,"Example New Shape")
+params {
+param "X" 0.0
+param "Y" 0.0
+param "Z" 0.0
+}
+
+ + + +Another way to create a new shape where you might want to add colors, UVs, materials and hard edges is to make a new shape using a e3d_object. + +

+(main-function
+    (lambda (*params* *extra-params*)
+        (define Fs ...) ; List of indices into vertice list
+        (define Vs ...) ; Vertice list
+        (define Efs (map (lambda (Indices)
+                           (make-e3d_face `(vs ,Indices))) Fs))
+        (define Mesh (make-e3d_mesh
+            `(type polygon)
+            `(vs ,Vs)
+            `(fs ,Efs)
+        ))
+        (define Obj (make-e3d_object `(obj ,Mesh)))
+        (define Mat '())
+        (list 'new_shape "MyShape" Obj Mat)
+        ))
+
+ + + diff --git a/plugins_src/scripting/docs/en/wscr/directives/adv_param.txt b/plugins_src/scripting/docs/en/wscr/directives/adv_param.txt new file mode 100644 index 000000000..2c87120b4 --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/directives/adv_param.txt @@ -0,0 +1,29 @@ +adv_param Directive +
+adv_params begin
+  {vframe, [
+     {hframe,[
+       {label,?__(3,"Path:")},
+       {text,"testing2",[{key,path}]}]},
+     panel,
+     separator,
+     {?__(2,"Enable"), 'true', [{key,enable}]}
+  ]}
+end.
+
+ +Adds parameters in the form of erlang term syntax, which allows +scripts to have more control over the user interface layout. The +erlang term syntax begins on the line after begin until +the line end.. NOTE that there must be a dot (.) immediately +after end on the same line, and end. must be on its own line. + +The erlang term itself should not have a dot after the +term itself. + +Locale strings ?__(1, "Text") and query values %[...] are available +to include inside the erlang term. + +An adv_param directive should be inside a +params { ... } section. + diff --git a/plugins_src/scripting/docs/en/wscr/directives/browse.txt b/plugins_src/scripting/docs/en/wscr/directives/browse.txt new file mode 100644 index 000000000..d5a905bbf --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/directives/browse.txt @@ -0,0 +1,11 @@ +browse Directive +
+browse "Click to browse" "filename.txt"
+
+ +A text box with a button to browse for files. + +A browse directive should be inside a +params { ... } section. + + diff --git a/plugins_src/scripting/docs/en/wscr/directives/checkbox.txt b/plugins_src/scripting/docs/en/wscr/directives/checkbox.txt new file mode 100644 index 000000000..079f3b674 --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/directives/checkbox.txt @@ -0,0 +1,10 @@ +checkbox Directive +
+checkbox "Enabled" 'true'
+
+ +Show a checkbox + +A checkbox directive should be inside a +params { ... } section. + diff --git a/plugins_src/scripting/docs/en/wscr/directives/command_changes.txt b/plugins_src/scripting/docs/en/wscr/directives/command_changes.txt new file mode 100644 index 000000000..b93bedd20 --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/directives/command_changes.txt @@ -0,0 +1,26 @@ +command_changes Directive +
+command_changes "points"
+
+ +For command scripts, sets a comma separated list of possible information +that will be changed by the script when making modifications to data from +command_inputs. This directive is not needed if the script uses we commands +to modify the shape directly. + +This directive is only used with command scripts, and only optionally as an +alternative to using the we commands directly. + +The e3d_mesh option provides an e3d mesh of the shape, which can be modified +by the script and then is recreated as a we object when returned back to wings. +Since the we object is changed to a e3d mesh and then back, it is possible that +all the numbering of vertices, edges and faces will be completely changed and any +special data that uses those numberings won't match anymore. + +Items can be: + points, + face_colors, + face_uvs, + e3d_mesh + + diff --git a/plugins_src/scripting/docs/en/wscr/directives/command_inputs.txt b/plugins_src/scripting/docs/en/wscr/directives/command_inputs.txt new file mode 100644 index 000000000..b06f27b68 --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/directives/command_inputs.txt @@ -0,0 +1,24 @@ +command_inputs Directive +
+command_inputs "points,faces"
+
+ +For command scripts, sets a comma separated list of input information that + will be sent to the script as part of the +extra_params variable in Python, or the +*extra-params* vaiable in Scheme. + For example, if faces is specified, an item entry with the key faces will + be available in extra params. This directive is not needed if the script +uses the we commands directly. + +This directive is only used with command scripts, and only optionally as an +alternative to using the we commands directly. + +Items can be: + points, + faces, + face_colors, + face_uvs, + e3d_mesh + + diff --git a/plugins_src/scripting/docs/en/wscr/directives/desc.txt b/plugins_src/scripting/docs/en/wscr/directives/desc.txt new file mode 100644 index 000000000..7f8b70428 --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/directives/desc.txt @@ -0,0 +1,11 @@ +desc Directive +
+desc ?__(2,"This script does something")
+
+ +A short description can be added to provide more information about what + the script does. + + + + diff --git a/plugins_src/scripting/docs/en/wscr/directives/export_param.txt b/plugins_src/scripting/docs/en/wscr/directives/export_param.txt new file mode 100644 index 000000000..fb13a2510 --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/directives/export_param.txt @@ -0,0 +1,21 @@ +export_param Instruction +
+export_param "subdivisions" %[params/subdivisions]
+
+ +Sets an export parameter for the wpa:export + function or some scripting export functions on the scripting plugin side. + For "script_texture_convert", the recommended option is +'user' with the export template for the texture handling. + +This directive is only used with exporter scripts. + +An export_param directive should be inside a +params_set { ... } section. + +The second argument can be a string, an atom or a query value enclosed +in %[...] + +Refer to the Query Mini Language about query values. + + diff --git a/plugins_src/scripting/docs/en/wscr/directives/extensions.txt b/plugins_src/scripting/docs/en/wscr/directives/extensions.txt new file mode 100644 index 000000000..5909371f0 --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/directives/extensions.txt @@ -0,0 +1,14 @@ +extensions Section +
+extensions {
+  ext ".tres" ?__(5,"Godot Mesh Library")
+}
+
+ +The extensions section indicates the extensions that are supported by the + importer or exporter script. + Each line within the section should be a ext directive, with its first + argument the extension including the dot, and the second argument is a + description of the file format. + + diff --git a/plugins_src/scripting/docs/en/wscr/directives/extra_file.txt b/plugins_src/scripting/docs/en/wscr/directives/extra_file.txt new file mode 100644 index 000000000..298ee91b5 --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/directives/extra_file.txt @@ -0,0 +1,17 @@ +extra_file Section +
+extra_file "icon" {
+  title "Choose small image"
+  extensions {
+    ...
+  }
+}
+
+ +An extra input file which can be used by any of the script modes. + The first argument should be the name that will be set in +extra_params for the script. + The title and the list of extensions need to be set for it. + + + diff --git a/plugins_src/scripting/docs/en/wscr/directives/hradio.txt b/plugins_src/scripting/docs/en/wscr/directives/hradio.txt new file mode 100644 index 000000000..eb4f29ba6 --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/directives/hradio.txt @@ -0,0 +1,19 @@ +hradio Directive +
+hradio "Color" 'blue' {
+  item "Red" 'red'
+  item "Green" 'green'
+  item "Blue" 'blue'
+}
+
+ +Shows a horizontal row of radio button choices. + +The list of values each start with item, followed +by the string showed to user, followed by an atom that +will be used by the script. + +A hradio directive should be inside a +params { ... } section. + + diff --git a/plugins_src/scripting/docs/en/wscr/directives/import_param.txt b/plugins_src/scripting/docs/en/wscr/directives/import_param.txt new file mode 100644 index 000000000..4de2f9e53 --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/directives/import_param.txt @@ -0,0 +1,20 @@ +import_param Instruction +
+import_param "script_texture_convert" 'auto'
+
+ +Sets an import parameter for the wpa:import + function or some scripting import functions on the scripting plugin side. + For "script_texture_convert", the recommended option is +'auto' for the texture handling. + +This directive is only used with importer scripts. + +An import_param directive should be inside a +params_set { ... } section. + +The second argument can be a string, an atom or a query value enclosed +in %[...] + +Refer to the Query Mini Language about query values. + diff --git a/plugins_src/scripting/docs/en/wscr/directives/include.txt b/plugins_src/scripting/docs/en/wscr/directives/include.txt new file mode 100644 index 000000000..a8d43e88b --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/directives/include.txt @@ -0,0 +1,22 @@ +include Directives +
+include "(%)_dialog.inclr"
+
+ +WSCR files can include other files, it is recommended to move all the parameter + related configuration to another file and add an include directive towards + it so as to keep the main wscr file small. + This is important as each time the user opens a script chooser dialog, + every directory is traversed for every wscr file, and each wscr file found + is read for it's information to display in the script chooser browser. + +The script chooser traversal process reads every wscr file, but it does + not read the include directives. + When a script is invoked, the include directives are used to read the rest + of the configuration. + A parenthesis wrapped percent symbol "(%)" + + auto expands to the name of the .wscr file before the extension. + + + diff --git a/plugins_src/scripting/docs/en/wscr/directives/menu.txt b/plugins_src/scripting/docs/en/wscr/directives/menu.txt new file mode 100644 index 000000000..58f4047c0 --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/directives/menu.txt @@ -0,0 +1,19 @@ +menu Directive +
+menu "Color" 'blue' {
+  item "Red" 'red'
+  item "Green" 'green'
+  item "Blue" 'blue'
+}
+
+ +Shows a drop down menu to select choices. + +The list of values each start with item, followed +by the string showed to user, followed by an atom that +will be used by the script. + +A menu directive should be inside a +params { ... } section. + + diff --git a/plugins_src/scripting/docs/en/wscr/directives/mode.txt b/plugins_src/scripting/docs/en/wscr/directives/mode.txt new file mode 100644 index 000000000..ce83b45fd --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/directives/mode.txt @@ -0,0 +1,33 @@ +mode Directive +
+mode "import"
+
+ +The mode indicates what kind of script it is and which menu it should +appear in. For example, if a script has "export" for mode, it will only +show up when choosing "Script-based exporters" in the +"File" > "Export" menu. + +Table of values for mode: +
+Kind of script         mode
+--------------         ----
+New shapes             (empty)
+Importers              import
+Exporters              export
+Commands               command
+
+ +New shapes scripts create a new shape. + +Importer scripts are Found in File > Import. + Returns an E3D file + +Exporter scripts are Found in File > Export. + Takes an E3D file. + +Command scripts changes existing shapes. + + + + diff --git a/plugins_src/scripting/docs/en/wscr/directives/name.txt b/plugins_src/scripting/docs/en/wscr/directives/name.txt new file mode 100644 index 000000000..8ca5e4e5f --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/directives/name.txt @@ -0,0 +1,7 @@ +name Directive +
+name ?__(1,"Raw Triangles (.raw)")
+
+ +A name for the script, which appears in the script chooser dialog. + diff --git a/plugins_src/scripting/docs/en/wscr/directives/param.txt b/plugins_src/scripting/docs/en/wscr/directives/param.txt new file mode 100644 index 000000000..90f5d5372 --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/directives/param.txt @@ -0,0 +1,10 @@ +param Directive +
+param "Size" "2.0"
+
+ +A param directive specifies one field for user input + +A param directive should be inside a +params { ... } section. + diff --git a/plugins_src/scripting/docs/en/wscr/directives/params.txt b/plugins_src/scripting/docs/en/wscr/directives/params.txt new file mode 100644 index 000000000..714b7e0de --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/directives/params.txt @@ -0,0 +1,11 @@ +params Section +
+params {
+  param "Size" "2.0"
+  ...
+}
+
+ +The params section contains param directives. + + diff --git a/plugins_src/scripting/docs/en/wscr/directives/params_init.txt b/plugins_src/scripting/docs/en/wscr/directives/params_init.txt new file mode 100644 index 000000000..c462ff647 --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/directives/params_init.txt @@ -0,0 +1,16 @@ +params_init Instructions Section +
+params_init {
+  do %[1>$'subdivisions']
+}
+
+ +The params_init section behaves as a subroutine, with each directive being +evaluated sequentially. This section is used to load values into temporary +variables to pass to the params and params_set sections. + +The state of temporary variables do not carry over after the params section. +The argument of a do should be a query value enclosed in %[...] + +Refer to the Query Mini Language about query values. + diff --git a/plugins_src/scripting/docs/en/wscr/directives/params_preview.txt b/plugins_src/scripting/docs/en/wscr/directives/params_preview.txt new file mode 100644 index 000000000..ce0401a8b --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/directives/params_preview.txt @@ -0,0 +1,9 @@ +params_preview Directive +
+params_preview 1
+
+ +Specifies that preview should be enabled on the dialog box so changes +are seen when when changing parameters. + + diff --git a/plugins_src/scripting/docs/en/wscr/directives/params_set.txt b/plugins_src/scripting/docs/en/wscr/directives/params_set.txt new file mode 100644 index 000000000..1d9afc7d9 --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/directives/params_set.txt @@ -0,0 +1,16 @@ +params_set Instructions Section +
+params_set {
+  export_param "include_uvs" %[bool(params/include_uvs)]
+  ...
+}
+
+ +The params_set section sets special parameters with directives such as +export_param, import_param and script_param. With each directive within +assigning a value to a parameter. These are different from the params +section which are for normal parameters shown in a dialog box. + +Query values are enclosed in %[...] brackets, refer to the +Query Mini Language about query values. + diff --git a/plugins_src/scripting/docs/en/wscr/directives/params_templates.txt b/plugins_src/scripting/docs/en/wscr/directives/params_templates.txt new file mode 100644 index 000000000..d763510af --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/directives/params_templates.txt @@ -0,0 +1,16 @@ +params_templates Section +
+params_templates {
+  template "export" ""
+}
+
+ +Param templates are standard groups of parameters that are used by most + plugins in Wings3D for consistency. + For example, importers might use the import template to show the flip axis + and scale parameters. + When a template is used, the corresponding code that interprets the standard + parameters to automatically modify the E3D tuple will also be handled by + the scripting interface. + + diff --git a/plugins_src/scripting/docs/en/wscr/directives/params_title.txt b/plugins_src/scripting/docs/en/wscr/directives/params_title.txt new file mode 100644 index 000000000..dfa034128 --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/directives/params_title.txt @@ -0,0 +1,9 @@ +params_title Directive +
+params_title ?__(3,"Command Settings")
+
+ +The params_title directive indicates the titlebar string of the dialog that + is shown for user input. + + diff --git a/plugins_src/scripting/docs/en/wscr/directives/script_param.txt b/plugins_src/scripting/docs/en/wscr/directives/script_param.txt new file mode 100644 index 000000000..21eff6078 --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/directives/script_param.txt @@ -0,0 +1,17 @@ +script_param Instruction +
+script_param "test" "1"
+
+ +Sets an extra parameter for the script, which has to be accessed through the +extra_params variable in Python, or the +*extra-params* variable in Scheme. + +An script_param directive should be inside a +params_set { ... } section. + +The second argument can be a string, an atom or a query value enclosed +in %[...] + +Refer to the Query Mini Language about query values. + diff --git a/plugins_src/scripting/docs/en/wscr/directives/template.txt b/plugins_src/scripting/docs/en/wscr/directives/template.txt new file mode 100644 index 000000000..fc548a406 --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/directives/template.txt @@ -0,0 +1,8 @@ +template Directive +
+template "export" ""
+
+ +A template in the params_template section. + + diff --git a/plugins_src/scripting/docs/en/wscr/directives/type.txt b/plugins_src/scripting/docs/en/wscr/directives/type.txt new file mode 100644 index 000000000..d97f6dcc6 --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/directives/type.txt @@ -0,0 +1,7 @@ +type Directive +
+type "py"
+
+ +This is either "scm" or "py", depending on if the script is a Scheme or Python file. + diff --git a/plugins_src/scripting/docs/en/wscr/directives/vradio.txt b/plugins_src/scripting/docs/en/wscr/directives/vradio.txt new file mode 100644 index 000000000..2b4dc7067 --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/directives/vradio.txt @@ -0,0 +1,19 @@ +vradio Directive +
+vradio "Color" 'blue' {
+  item "Red" 'red'
+  item "Green" 'green'
+  item "Blue" 'blue'
+}
+
+ +Shows a vertical list of radio button choices. + +The list of values each start with item, followed +by the string showed to user, followed by an atom that +will be used by the script. + +A vradio directive should be inside a +params { ... } section. + + diff --git a/plugins_src/scripting/docs/en/wscr/intro.txt b/plugins_src/scripting/docs/en/wscr/intro.txt new file mode 100644 index 000000000..47975f516 --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/intro.txt @@ -0,0 +1,13 @@ +What are .wscr files? + +Wings SCRipt files provide information so that scripts can be used like normal plugins in wings, WSCR files specify: + +* Name and description of the script. +* If the script is a import, export, shape or command script. +* Parameters to ask the user before starting the script. + +When a user opens a script chooser dialog, the script search path is traversed for all wscr files, the name in the wscr file is used for the name shown in the script chooser dialog. + + + + diff --git a/plugins_src/scripting/docs/en/wscr/langfiles.txt b/plugins_src/scripting/docs/en/wscr/langfiles.txt new file mode 100644 index 000000000..b0de5a53a --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/langfiles.txt @@ -0,0 +1,22 @@ +Language files + +Like normal wings3d plugins, wscr files can use language files to localize the user interface text into multiple languages. The lang file needs to be in the same directory as the wscr file, and begin with the same base file name without the extension, with a underscore and a language code following it, followed by the ".lang" file extension. The format of the language file is the same Erlang term format as wings3d lang files. To localize a string in the wscr file, enclose the string in the same enclosing syntax used in Erlang wings3d source files. + +
+name ?__(1,"Color")
+
+ +For a script called "script_name.wscr", the lang file for French should be named "script_name_fr.lang", and the content resembles other Wings3D lang files but the outer name is script and the inner name is wscr: + +
+{script,
+ [
+  {wscr,
+   [
+    {1,"Couleur"}
+   ]}
+ ]}.
+
+ + + diff --git a/plugins_src/scripting/docs/en/wscr/pluginscripts.txt b/plugins_src/scripting/docs/en/wscr/pluginscripts.txt new file mode 100644 index 000000000..cb95e0c40 --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/pluginscripts.txt @@ -0,0 +1,67 @@ +Plugin Scripts + +Plugin scripts are scripts that get added into wing's menu system so they can be accessed the same way as plugins, this is done by having a config file that points to the wscr file, and some information of the menu to create for the script. + +Each plugin script config file is written in erlang term syntax, similar to the syntax used for Wings3D language files. + +
+%% Syntax of conf file, comment lines start with %
+
+{<Name>, [
+    {wscr, <WSCRFile>},
+    {menu, [
+        {<MenuPath>, <Option>}
+    ]}
+]}.
+
+{strings, [
+    {plugin, <Name>},
+    {<LangCode>, [
+        {{plugin,<MenuPathItem>}, <ActualName>},
+        {{menu,<MenuPathItem>}, <ActualName>},
+        ...
+        {{info,<MenuPathItem>}, <Text>}
+    ]}
+]}.
+
+ +<Name> is a double quoted string of a unique name, for example "com.example.author-name.plugin-script-name" or "author-name.plugin-script-name". + +<WSCRFile> is a double quoted path relative to the current config file, that points to the script's .wscr file. + +<MenuPath> is a list of <MenuPathItem> enclosed in [...] brackets, with comma separated words of the menu identifiers that the plugin's menu item is located. The first item is the top menu, subsequent items before the last item are submenus and the last item is the name for the plugin itself. + +<MenuPathItem> is each individual menu path item, names should not have quotes, double quotes, special characters, and just use a-z and _, numbers cannot be the first character in the name. + +<Option> is currently an empty list [] + +<LangCode> is a 2 or 5 character language code, same as the ones available in Wings 3D. + +<ActualName> is a double quote enclosed text of the actual display name for the plugin or sub menu item that will be displayed in the menu. + +<Text> is a double quote enclosed text of text to show in some contexts. + +The config file is placed in the "plugin_scripts" folder inside the wings user data folder, in the same directory as the plugins and patches folders. + +
+%% Example plugin script .conf file:
+
+{"com.example.my.plugin.1", [
+    {wscr, "my-plugin.wscr"},
+    {menu, [
+        {[tools, newsubmenu, newnewsubmenu, myplugin], []}
+    ]}
+]}.
+
+{strings, [
+    {plugin, "com.example.my.plugin.1"},
+    {"en", [
+        {{plugin,name}, "My Plugin"},
+        {{menu,newsubmenu}, "New Submenu"},
+        {{menu,newnewsubmenu}, "New new Submenu"},
+        {{menu,myplugin}, "My Plugin"},
+        {{info,myplugin}, "My Plugin menu tooltip"}
+    ]}
+]}.
+
+ diff --git a/plugins_src/scripting/docs/en/wscr/query.txt b/plugins_src/scripting/docs/en/wscr/query.txt new file mode 100644 index 000000000..0d5dfcfdb --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/query.txt @@ -0,0 +1,246 @@ +Query Mini Language + +The query language is designed to be able to navigate the Erlang terms used by Wings3D with a simple path-like syntax. + +When the user enters parameter settings, before the script is invoked, there will be a need to set some settings for wpa:export or wpa:import through export_param and import_param instructions. The query language is used for referencing parameter values in the script information file as well as for communicating between the script and plugin. + +Query values are enclosed in %[...] brackets in the .wscr file instead of double quotes. + +

First Identifier

+ +The first identifier in the query can be an identifier such as st, params, a temporary variable retrieval, a function call, a tuple/list constructor or a literal such as 1, 'atom' or "string". + + +

Atoms

+ +Atoms must be enclosed in single quotes, words without single quotes are not automatically made into atoms. + +e.g. + +
+    'triangulated'
+
+ + +

Strings

+ +Strings are enclosed in double quotes. + +e.g. + +
+    "Sample text"
+
+ + + +

Numbers

+ +Integers and floating point numbers can be used in the query. + +e.g. + +
+    1
+    1.2
+
+ + +

Dot Notation

+ +
+    variable.shapes
+
+ +With some records such as 'st' and 'we', fields of those records can be accessed using a dot notation. There is no need to specify the record type itself as done in Erlang because the record is inspected at runtime to match the record type for a field name. + +However, only a few record types have their fields registered in the plugin to use the dot notation. Alternatively, record fields can be accessed with the tuple index operator, where {0} is the atom of the record and {1} is the first field. + +e.g. + +
+    st.shapes
+
+ + +

Associative List Path

+ +
+    variable/key
+
+ +Associative lists can be navigated by their keys with the "slash" (/) character. If the associated lists contain more associated lists, the slash can be chained like a path to access deeper elements. + +e.g. + +
+    params/include_uvs
+
+ + +

Tuple by index

+ +
+    variable{0}
+
+ +The entries of a tuple can be accessed by a zero-based index by enclosing a number inside curly brackets, the first entry is {0}. Indexing operators can be chained as necessary to access deeper elements. + +e.g. + +
+    tuple(1,2,3,4){2}
+
+ + + + +

List by index

+ +
+    variable[0]
+
+ +The entries of a list can be accessed by a zero-based index by enclosing a number inside square brackets, the first entry is [0] Indexing operators can be chained as necessary to access deeper elements. + +e.g. + +
+    list(1,2,3,4)[1]
+
+ + + + +

Store to

+ +
+    >$'variable'
+
+ +Store the current value into a temporary variable for later retrieval, the syntax is a greater than symbol, a dollar sign and a integer or atom literal. This operator returns the same value that it is storing so it can be added at the end of a query chain without affecting the return value. + +e.g. + +
+    params/include_uvs>$'include_uvs'
+
+ + + + +

Get from

+ +
+    <$'variable'
+
+ +Get the value stored in a temporary variable, the syntax is a lesser than symbol, a dollar sign and a integer or atom literal, this can appear at the beginning of the query. + +e.g. + +
+    <$'settings'/group1/setting
+
+ + +

Call function

+ +
+    module:function( ... )
+
+ +Calls a function from a module, with the given arguments. A function call can appear at the beginning of the query. + +e.g. + +
+    lists:reverse(list(1,2,3,4))
+
+ + + +

List constructor

+ +
+    list(1,'two',3)
+
+ +Construct a list with given values. A list constructor can appear at the beginning of the query. It is usually more convenient to use %setvar with temporary variables to input list data structures from the script to the plugin than to use inline constructors. + + + + +

Tuple constructor

+ +
+    tuple(1,2,'three')
+
+ +Construct a tuple with given values. A tuple constructor can appear at the beginning of the query. It is usually more convenient to use %setvar with temporary variables to input tuple data structures from the script to the plugin than to use inline constructors. + + + + +

Boolean Value

+ +
+    bool(1)
+
+ +Get either the atom 'true' or 'false' from another value. + + + + +

Integer Value

+ +
+    int("1")
+
+ +Get the integer value from another value + + + + +

Float Value

+ +
+    float("1.0")
+
+ +Get the float value from another value. + + + +

Ok Test

+ +
+    ok_test(orddict:find(key, $<'variable'), "not found")
+
+ +Tests if the return of the first argument is a tuple with the first element being 'ok'. If it is an 'ok' tuple, the tuple's value is returned. If the first argument is not an 'ok' tuple, the second argument is evaluated and returned instead. + + + +

Value Test

+ +
+    value_test('value', gb_trees:lookup(key, $<'variable'), "not found")
+
+ +A general version of ok_test that tests if the return of the second argument is a tuple with the first element being equal to the first argument. If the second argument does not match, the third argument is evaluated and returned instead. + + + +

Conditional If

+ +
+    if(bool(<$'cond'), "on", "off")
+
+ +Tests the first argument if it evaluates to the atom 'true', then the second argument (the "then" argument) is evaluated, otherwise the third (the "else" argument) is evaluated. + + + diff --git a/plugins_src/scripting/docs/en/wscr/scriptfolders.txt b/plugins_src/scripting/docs/en/wscr/scriptfolders.txt new file mode 100644 index 000000000..d43b65a93 --- /dev/null +++ b/plugins_src/scripting/docs/en/wscr/scriptfolders.txt @@ -0,0 +1,37 @@ +Script Folders + +Script folder files are config files in the user data area where other software or the user can add new paths to find scripts. + +Each script folder config file is written in erlang term syntax, similar to the syntax used for Wings3D language files. + +
+%% Syntax of config file, comment lines start with %
+
+{<Name>,[
+    {type,<Type>},
+    {path,<Paths>}
+]}.
+
+ +<Name> is a double quoted string of a unique name, for example "com.example.author.script-folders.name" or "author-name.script-folders.name". + +<Type> is a list enclosed in [...] of: vertex, edge, face, body, shape + +<Paths> is a list of double quoted string paths enclosed in [...]. So a list of one path is ["folder/"]. Paths can be absolute paths, or relative to your operating system user folder. + +The config file is placed in the "script_folders" folder inside the wings user data folder, in the same directory as the plugins and patches folders. + +
+%% Example script folder .conf file:
+
+{"com.example.wings-scripts-1",[
+    {type,[shape]},
+    {path,["a1/","a2/"]}
+]}.
+
+{"com.example.scripts.2",[
+    {type,[face,body]},
+    {path,["a/"]}
+]}.
+
+ diff --git a/plugins_src/scripting/docs/funs b/plugins_src/scripting/docs/funs new file mode 100644 index 000000000..1962de825 --- /dev/null +++ b/plugins_src/scripting/docs/funs @@ -0,0 +1,301 @@ +| Winged Edge Function Reference +we | collapse | collapse_edge | ! | EdgeNumber | KeepVertexNumber | | +we | collapse | collapse_edges | ! | ListOfEdges | | | +we | collapse | collapse_faces | ! | ListOfFaces | | | +we | collapse | collapse_vertices | ! | ListOfVertices | | | +we | collapse | fast_collapse_edge | ! | EdgeNumber | | | +we | dissolve | complement | ! | ListOfFaces | | | +we | dissolve | faces | ! | ListOfFaces | | | +we | dissolve | outer_edge_partition | | ListOfFaces | | list + +we | edge | cut | ! | EdgeNumber Count | | number +we | edge | dissolve_edge | ! | EdgeNumber | | number + +we | edge | dissolve_edges | ! | ListOfEdgeNumbers | | _ +we | edge | dissolve_edges | ! | ListOfEdgeNumbers ListOfFaceNumbers | | list + +we | edge | dissolve_isolated_vs | ! | ListOfVertexNumbers | | | +we | edge | fast_cut | ! | EdgeNumber VertexPositionVec3 | | vertex number +we | edge | from_faces | | ListOfFaceNumbers | | list of edge numbers +we | edge | from_vs | | ListOfVertexNumbers | | list of edge numbers +we | edge | length | | EdgeNumber | | float +we | edge | reachable_faces | | ListOfFaceNumbers ListOfEdgeNumbers | | list of face numbers +we | edge | screaming_cut | ! | EdgeNumber VertexPositionVec3 | | vertex number +we | edge | select_region | | ListOfEdgeNumbers | | list of face numbers +we | edge | to_vertices | | ListOfEdges | | list of vertex numbers + +we | edge_cmd | loop_cut_partition | | ListOfEdgeNumbers | | list of lists of face numbers + +we | edge_loop | edge_links | | ListOfEdgeNumbers | | list of tuples $(TUPLE EdgeNumber VertexNumberStart VertexNumberEnd) +we | edge_loop | edge_loop_vertices | | ListOfEdgeNumbers | | list of lists of vertex numbers +we | edge_loop | partition_edges | | ListOfEdgeNumbers | | list of lists of edge numbers +we | edge_loop | select_loop | | ListOfEdgeNumbers | | list of edge numbers + +we | extrude_edge | extrude_edges | ! | ListOfEdgeNumbers DistanceFloat | | list of vertices, list of new vertices, list of forbidden faces +we | extrude_face | faces | ! | ListOfFaceNumbers | | | +we | extrude_face | regions | ! | ListOfListOfFaceNumbers | | | + +we | face | are_neighbors | | FaceNumberA FaceNumberB | | boolean +we | face | area | | FaceNumber | | float +we | face | center | | FaceNumber | | vec3 +we | face | delete_bad_faces | ! | ListOfFaceNumbers | | +we | face | extend_border | | ListOfFaceNumbers | | list of face numbers +we | face | face_normal_ccw | | ListOfVertexNumbers | | vec3 +we | face | face_normal_cw | | ListOfVertexNumbers | | vec3 +we | face | from_edges | | ListOfEdgeNumbers | | list +we | face | from_vs | | ListOfVertexNumbers | | list +we | face | good_normal | | FaceNumber | | boolean +we | face | inner_edges | | ListOfFaceNumbers | | list of inner edges +we | face | inner_outer_edges | | ListOfFaceNumbers | | list of inner edges, list of outer edges +we | face | is_planar | | ToleranceFloat FaceNumber | | boolean +we | face | is_thin | | FaceNumber | | boolean +we | face | mirror_matrix | | FaceNumber | | matrix +we | face | normal | | FaceNumber | EdgeNumber | vec3 +we | face | outer_edges | | ListOfFaceNumbers | | list of edge numbers +we | face | to_edges | | ListOfFaceNumbers | | list of edge numbers +we | face | to_vertices | | ListOfFaceNumbers | | list of vertex numbers +we | face | vertex_positions | | FaceNumber | EdgeNumber | list +we | face | vertices | | FaceNumber | | number +we | face | vertices_ccw | | FaceNumber | EdgeNumber | list of vertex numbers +we | face | vertices_cw | | FaceNumber | EdgeNumber | list of vertex numbers + +we | face_cmd | force_bridge | ! | FaceNumberA VertexA FaceNumberB VertexB | | | +we | face_cmd | mirror_faces | ! | ListOfFaceNumbers | | | + +we | facemat | all | | | | list +we | facemat | any_interesting_materials | | | | boolean +we | facemat | assign | ! | ListOfTuples | | | +we | facemat | assign | ! | Material FaceNumber | | | +we | facemat | delete_face | ! | FaceNumber | | | +we | facemat | delete_faces | ! | ListOfFaceNumbers | | | +we | facemat | face | | FaceNumber | | atom string +we | facemat | gc | ! | | | | +we | facemat | hide_faces | ! | | | | +we | facemat | is_material_used | | Material | | boolean +we | facemat | keep_faces | ! | ListOfFaceNumbers | | +we | facemat | mat_faces | | ListOfPairsFaceInfo | | list of tuples $(TUPLE Material ListOfPairsFaceInfo) +we | facemat | show_faces | ! | ListOfFaces | | | +we | facemat | used_materials | | | | list + +we | subdiv | get_proxy_info | | DynListOfVertexNumbers UpdateListOfVertexNumbers | | list of face positions, list of edge split, list of smooth new vertices, list of original vertices +we | subdiv | smooth | ! | | | | +we | subdiv | smooth | ! | ListOfFaces ListOfVertices ListOfEdges ListOfHardEdges | | +we | subdiv | smooth_faces_htab | | | | list of faces and list of hard edges +we | subdiv | subdiv | ! | | | | +we | subdiv | subdiv | ! | ListOfFaces ListOfVertices ListOfEdges ListOfHardEdges | | + +we | tesselation | quadrangulate | ! | | ListOfFaceNumbers | | +we | tesselation | triangulate | ! | | ListOfFaceNumbers | | + +we | util | add_vpos | | ListOfVertexNumbers | | list of tuple $(TUPLE VertexNumber Vec3) +we | util | update_vpos | | ListOfTuple | | list of tuple $(TUPLE VertexNumber Vec3) +| Each element in $(ARG ListOfTuple) is $(TUPLE VertexNumber Vec3) + +we | va | all | | WhatAtom | | list +we | va | any_attributes | | | | boolean +we | va | any_colors | | | | boolean +we | va | any_uvs | | | | boolean + +we | va | del_edge_attrs | ! | EdgeNumber | | + +we | va | edge_attrs | | EdgeNumber Side | WeightFloat | Attribute value +we | va | face_attr | | WhatSymbol FaceNumber | EdgeNumber | list +| $(ARG WhatSymbol) can be $(ATOM uv) or $(ATOM color) + +we | va | face_mixed_attrs | | FaceNumber | | list +we | va | face_pos_attr | | WhatAtom FaceNumber EdgeNumber | | list, list +we | va | gc | ! | | | | +we | va | remove | ! | WhatSymbol | ListOfFaceNumbers | +| $(ARG WhatSymbol) can be $(ATOM all) $(ATOM uv) $(ATOM color) + +we | va | renumber | ! | ListOfPairs | | | +we | va | set_body_color | ! | Vec3 | | | +we | va | set_both_edge_attrs | ! | EdgeNumber LeftOpaqueAttribute RightOpaqueAttribute | | | +we | va | set_edge_attrs | ! | ListOfTuple | | | +| Each element in $(ARG ListOfTuple) is $(TUPLE EdgeNumber LeftUV RightUV LeftVC RightVC) + +we | va | set_edge_attrs | ! | EdgeNumber Side AttrValue | | +we | va | set_edge_color | ! | ListOfEdges ColorVec3 | | +we | va | set_edge_color | ! | EdgeNumber LeftColorVec3 RightColorVec3 | | +we | va | set_edge_colors | ! | ListOfTuple | | +| Each element in $(ARG ListOfTuple) is $(TUPLE EdgeNumber LeftColor RightColor) + +we | va | set_edge_uvs | ! | ListOfTuple | | +| Each element in $(ARG ListOfTuple) is $(TUPLE EdgeNumber LeftUV RightUV) + +we | va | set_face_attr_vs | ! | WhatAtom FaceNumber ListOfNewValues | | +we | va | set_face_attrs | ! | FaceNumber OpaqueAttr | | +we | va | set_face_color | ! | ListOfFaceNumbers Vec3 | | +we | va | set_vertex_color | ! | ListOfVertexNumbers Vec2 | | +we | va | set_vtx_face_uvs | ! | VertexNumber ListOfFaceNumbers Vec2 | | +we | va | vtx_attrs | | VertexNumber | FaceNumber | Attribute value + +we | vertex | bounding_box | | | ListOfVertices BoundingBoxVec3 | vec3 +we | vertex | center | | | ListOfVertices | vec3 +we | vertex | connect | ! | FaceNumber ListOfVertexNumbers | | +we | vertex | connect_cut | ! | VertexNumberA VertexNumberB | | list of edge numbers +we | vertex | dissolve_isolated | ! | ListOfVertexNumbers | | +we | vertex | edge_through | | VertexNumberA VertexNumberB | FaceNumber | $(ARG EdgeNumber) +we | vertex | flatten | ! | ListOfVertices PlaneNormalVec3 | CenterVec3 | +we | vertex | force_connect | ! | VertexNumberStart VertexNumberEnd FaceNumber | | | +we | vertex | from_edges | | ListOfEdgeNumbers | | list +we | vertex | from_faces | | ListOfFaceNumbers | | list +we | vertex | isolated | | | | list of vertex numbers +we | vertex | normal | | VertexNumber | | vec3 +we | vertex | outer_vertices_ccw | | ListOfFaceNumbers | | list of vertex numbers or none +we | vertex | per_face | | ListOfVertexNumbers | | list of tuples $(TUPLE Face ListOfVertexNumbers) +we | vertex | pos | | VertexNumber | | vec3 +we | vertex | reachable | | ListOfVertexNumbers | | list + +we | vertex_cmd | bevel_vertex | ! | VertexNumber | | +we | vertex_cmd | connect | ! | ListOfVertices | | + +we | we | all_hidden | | | | boolean +we | we | break_mirror | ! | | | | +we | we | centroid | | | | vec3 +we | we | create_holes | ! | ListOfFaceNumbers | | +we | we | create_mirror | ! | FaceNumber | | +we | we | fast_rebuild | ! | | | | +we | we | freeze_mirror | ! | | | | +we | we | fully_visible_edges | | List | | list +we | we | hide_faces | ! | ListOfFaces | | list +we | we | invert_normals | ! | | | | +we | we | is_consistent | | | | boolean +we | we | is_face_consistent | | FaceNumber | | boolean +we | we | is_open | | | | boolean +we | we | mirror_projection | | | | matrix +we | we | new_id | ! | | | number +we | we | new_ids | ! | Number | | +we | we | new_wrap_range | ! | Number Number | | tuple +we | we | normals | | FaceNormals MatrixOrNone | | list +we | we | num_hidden | | | | number +we | we | perimeter | | | | float +we | we | rebuild | ! | | | | +we | we | renumber | ! | StartNumber | RootSet | +we | we | separate | ! | | | list +we | we | show_faces | ! | | ListOfFaces | +we | we | surface_area | | | | float +we | we | transform_vs | ! | Matrix | | +we | we | uv_mapped_faces | | | | list +we | we | uv_to_color | ! | | | | +we | we | validate_mirror | ! | | | | +we | we | visible | | | List | list +| $(ARG List) is either list of face numbers, or list of tuples $(TUPLE FaceNumber EdgeNumber) + +we | we | visible_edges | | | ListOfEdges | list +we | we | visible_vs | | | ListOfVertices | list +we | we | volume | | | | float + +| e3d_mat | identity | | | | matrix +| e3d_mat | is_identity | | Matrix | | bool +| e3d_mat | determinant | | Matrix | | float +| e3d_mat | print | | Matrix | | atom +| e3d_mat | compress | | Matrix | | matrix +| e3d_mat | expand | | Matrix | | matrix + +| e3d_mat | translate | | Vec3 | | _ +| e3d_mat | translate | | Float Float Float | | matrix + +| e3d_mat | scale | | Vec3OrFloat | | _ +| e3d_mat | scale | | Float Float Float Float | | matrix + +| e3d_mat | rotate | | Float Vec3 | | matrix + +| e3d_mat | rotate_from_euler_rad | | Vec3 | | +| e3d_mat | rotate_from_euler_rad | | Float Float Float | | matrix + +| e3d_mat | rotate_to_z | | Vec3 | | matrix +| e3d_mat | rotate_s_to_t | | Vec3 Vec3 | | matrix +| e3d_mat | project_to_plane | | Vec3 | | matrix +| e3d_mat | transpose | | Matrix | | matrix +| e3d_mat | invert | | Matrix | | matrix +| e3d_mat | add | | Matrix Matrix | | matrix + +| e3d_mat | mul | | List | | _ +| e3d_mat | mul | | Matrix MatrixOrFloat | | matrix + +| e3d_mat | mul_point | | Matrix Vec3 | | vec3 +| e3d_mat | mul_vector | | Matrix Vec3 | | vec3 +| e3d_mat | eigenv3 | | Matrix | | vec3 matrix + + +| e3d_vec | zero | | | | vec3 +| e3d_vec | is_zero | | Vec3 | | bool + +| e3d_vec | add | | List | | _ +| e3d_vec | add | | Vec3 Vec3 | | vec3 + +| e3d_vec | add_prod | | Vec3 Vec3 Float | | vec3 + +| e3d_vec | sub | | List | | _ +| e3d_vec | sub | | Vec3 Vec3 | | vec3 + +| e3d_vec | lerp | | Vec3 Vec3 Float | | vec3 +| e3d_vec | norm_sub | | Vec3 Vec3 | | vec3 +| e3d_vec | mul | | Vec3 Float | | vec3 +| e3d_vec | divide | | Vec3 Float | | vec3 +| e3d_vec | neg | | Vec3 | | vec3 +| e3d_vec | dot | | Vec3 Vec3 | | float + +| e3d_vec | cross | | Vec3 Vec3 | | vec3 +| e3d_vec | len | | Vec3 | | float +| e3d_vec | len_sqr | | Vec3 | | float +| e3d_vec | dist | | Vec3 Vec3 | | float +| e3d_vec | dist_sqr | | Vec3 Vec3 | | float + +| e3d_vec | norm | | Vec3 | | _ +| e3d_vec | norm | | Float Float Float | | vec3 + +| e3d_vec | normal | | List | | _ +| e3d_vec | normal | | Vec3 Vec3 Vec3 | | vec3 + +| e3d_vec | average | | List | | _ +| e3d_vec | average | | Vec3 Vec3 | | _ +| e3d_vec | average | | Vec3 Vec3 Vec3 Vec3 | | vec3 + +| e3d_vec | area | | Vec3 Vec3 Vec3 | | float +| e3d_vec | degrees | | Vec3 Vec3 | | float + +| e3d_vec | plane | | List | | _ +| e3d_vec | plane | | Vec3 Vec3 | | _ +| e3d_vec | plane | | Vec3 Vec3 Vec3 | | tuple + +| e3d_vec | plane_side | | Vec3 Tuple | | integer +| e3d_vec | plane_dist | | Vec3 Tuple | | float +| e3d_vec | line_dist | | Vec3 Vec3 Vec3 | | float +| e3d_vec | line_dist_sqr | | Vec3 Vec3 Vec3 | | float +| e3d_vec | line_line_intersect | | Vec3 Vec3 Vec3 Vec3 | | any +| e3d_vec | project_2d | | Vec3 Atom | | vec3 +| Atom is $(ATOM x), $(ATOM y) or $(ATOM z) + +| e3d_vec | largest_dir | | Vec3 | | atom + + +| e3d_bv | box | | | | _ +| e3d_bv | box | | List | | _ +| e3d_bv | box | | Any Any | | _ +| e3d_bv | box | | Vec3 Vec3 Float | | tuple + +| e3d_bv | union | | Tuple Tuple | | tuple +| e3d_bv | dist | | Tuple Tuple | | float_or_false +| e3d_bv | intersect | | Tuple Tuple | | bool +| e3d_bv | center | | Tuple | | vec3 +| e3d_bv | max_extent | | Tuple | | any +| e3d_bv | surface_area | | Tuple | | float +| e3d_bv | volume | | Tuple | | float +| e3d_bv | sphere | | Tuple | | tuple +| e3d_bv | inside | | Vec3 Tuple | | bool + +| e3d_bv | hit | | Ray BBox | | _ +| e3d_bv | hit | | Ray Tuple BBox | | bool + +| e3d_bv | inv_sign | | Vec3 | | tuple +| e3d_bv | eigen_vecs | | List | | tuple +| e3d_bv | quickhull | | List | | tuple +| e3d_bv | covariance_matrix | | List | | tuple + + + + + + diff --git a/plugins_src/scripting/init_scripts/callable.conf b/plugins_src/scripting/init_scripts/callable.conf new file mode 100644 index 000000000..c02383c84 --- /dev/null +++ b/plugins_src/scripting/init_scripts/callable.conf @@ -0,0 +1,272 @@ + +%% +%% Scripting for Shapes +%% +%% Copyright 2025 Edward Blake +%% +%% See the file "license.terms" for information on usage and redistribution +%% of this file, and for a DISCLAIMER OF ALL WARRANTIES. +%% +%% $Id$ +%% + +%% List of callable wings functions +[ +{{wings_collapse,collapse_edge,1},{[int,we],we}}, +{{wings_collapse,collapse_edge,2},{[int,int,we],we}}, +{{wings_collapse,collapse_edges,1},{[list,we],we}}, +{{wings_collapse,collapse_faces,1},{[gbset,we],[we,gbset]}}, +{{wings_collapse,collapse_vertices,1},{[list,we],we}}, +{{wings_collapse,fast_collapse_edge,1},{[int,we],we}}, +{{wings_dissolve,complement,1},{[list,we],we}}, +{{wings_dissolve,faces,1},{[ordset,we],we}}, +{{wings_dissolve,outer_edge_partition,1},{[ordset,we],list}}, +{{wings_edge,cut,2},{[int,int,we],[we,int]}}, +{{wings_edge,dissolve_edge,1},{[int,we],we}}, +{{wings_edge,dissolve_edges,1},{[list,we],we}}, +{{wings_edge,dissolve_edges,2},{[list,list,we],[we,list]}}, +{{wings_edge,dissolve_isolated_vs,1},{[list,we],we}}, +{{wings_edge,fast_cut,2},{[int,vec3_or_atom,we],[we,int]}}, +{{wings_edge,from_faces,1},{[list,we],gbset}}, +{{wings_edge,from_vs,1},{[list,we],list}}, +{{wings_edge,length,1},{[int,we],float}}, +{{wings_edge,reachable_faces,2},{[gbset,gbset,we],list}}, +{{wings_edge,screaming_cut,2},{[int,vec3,we],[we,int]}}, +{{wings_edge,select_region,1},{[gbset,we],gbset}}, +{{wings_edge,to_vertices,1},{[list,we],list}}, +{{wings_edge_cmd,loop_cut_partition,1},{[list,we],list}}, +{{wings_edge_loop,edge_links,1},{[gbset,we],list}}, +{{wings_edge_loop,edge_loop_vertices,1},{[gbset,we],list}}, +{{wings_edge_loop,partition_edges,1},{[gbset,we],list}}, +{{wings_edge_loop,select_loop,1},{[ordset,we],list}}, +{{wings_extrude_edge,extrude_edges,2},{[list,float,we],[we,list,gbset,gbset]}}, +{{wings_extrude_face,faces,1},{[list,we],we}}, +{{wings_extrude_face,regions,1},{[list,we],we}}, +{{wings_face,are_neighbors,2},{[int,int,we],bool}}, +{{wings_face,area,1},{[int,we],float}}, +{{wings_face,center,1},{[int,we],vec3}}, +{{wings_face,delete_bad_faces,1},{[list,we],we}}, +{{wings_face,extend_border,1},{[gbset,we],gbset}}, +{{wings_face,face_normal_ccw,1},{[list,we],vec3}}, +{{wings_face,face_normal_cw,1},{[list,we],vec3}}, +{{wings_face,from_edges,1},{[list,we],gbset}}, +{{wings_face,from_vs,1},{[list,we],list}}, +{{wings_face,good_normal,1},{[int,we],bool}}, +{{wings_face,inner_edges,1},{[list,we],list}}, +{{wings_face,inner_outer_edges,1},{[list,we],[list,list]}}, +{{wings_face,is_planar,2},{[float,int,we],bool}}, +{{wings_face,is_thin,1},{[int,we],bool}}, +{{wings_face,mirror_matrix,1},{[int,we],matrix}}, +{{wings_face,normal,1},{[int,we],vec3}}, +{{wings_face,normal,2},{[int,int,we],vec3}}, +{{wings_face,outer_edges,1},{[list,we],list}}, +{{wings_face,to_edges,1},{[list,we],list}}, +{{wings_face,to_vertices,1},{[list,we],list}}, +{{wings_face,vertex_positions,1},{[int,we],list}}, +{{wings_face,vertex_positions,2},{[int,int,we],list}}, +{{wings_face,vertices,1},{[int,we],int}}, +{{wings_face,vertices_ccw,1},{[int,we],list}}, +{{wings_face,vertices_ccw,2},{[int,int,we],list}}, +{{wings_face,vertices_cw,1},{[int,we],list}}, +{{wings_face,vertices_cw,2},{[int,int,we],list}}, +{{wings_face_cmd,force_bridge,4},{[int,int,int,int,we],we}}, +{{wings_face_cmd,mirror_faces,1},{[list,we],we}}, +{{wings_facemat,all,0},{[we],list}}, +{{wings_facemat,any_interesting_materials,0},{[we],bool}}, +{{wings_facemat,assign,1},{[list,we],we}}, +{{wings_facemat,assign,2},{[val,list,we],we}}, +{{wings_facemat,delete_face,1},{[int,we],we}}, +{{wings_facemat,delete_faces,1},{[list,we],we}}, +{{wings_facemat,face,1},{[int,we],atom}}, +{{wings_facemat,gc,0},{[we],we}}, +{{wings_facemat,hide_faces,0},{[we],we}}, +{{wings_facemat,is_material_used,1},{[val,we],bool}}, +{{wings_facemat,keep_faces,1},{[list,we],we}}, +{{wings_facemat,mat_faces,1},{[list,we],list}}, +{{wings_facemat,show_faces,1},{[list,we],we}}, +{{wings_facemat,used_materials,0},{[we],list}}, +{{wings_subdiv,get_proxy_info,2},{[list,list,we],[list,list,list,list]}}, +{{wings_subdiv,smooth,0},{[we],we}}, +{{wings_subdiv,smooth,4},{[list,list,list,list,we],we}}, +{{wings_subdiv,smooth_faces_htab,0},{[we],[list,gbset]}}, +{{wings_subdiv,subdiv,0},{[we],we}}, +{{wings_subdiv,subdiv,4},{[list,list,list,list,we],we}}, +{{wings_tesselation,quadrangulate,0},{[we],we}}, +{{wings_tesselation,quadrangulate,1},{[list,we],we}}, +{{wings_tesselation,triangulate,0},{[we],we}}, +{{wings_tesselation,triangulate,1},{[gbset,we],we}}, +{{wings_util,add_vpos,1},{[list,we],list}}, +{{wings_util,update_vpos,1},{[list,we],list}}, +{{wings_va,all,1},{[atom,we],list}}, +{{wings_va,any_attributes,0},{[we],bool}}, +{{wings_va,any_colors,0},{[we],bool}}, +{{wings_va,any_uvs,0},{[we],bool}}, +{{wings_va,del_edge_attrs,1},{[int,we],we}}, +{{wings_va,edge_attrs,2},{[int,atom,we],attr}}, +{{wings_va,edge_attrs,3},{[int,atom,float,we],attr}}, +{{wings_va,face_attr,2},{[atom,int,we],list}}, +{{wings_va,face_attr,3},{[atom,int,int,we],list}}, +{{wings_va,face_mixed_attrs,1},{[int,we],list}}, +{{wings_va,face_pos_attr,3},{[atom,int,int,we],[list,list]}}, +{{wings_va,gc,0},{[we],we}}, +{{wings_va,remove,1},{[atom,we],we}}, +{{wings_va,remove,2},{[atom,list,we],we}}, +{{wings_va,renumber,1},{[gbtree,we],we}}, +{{wings_va,set_body_color,1},{[vec3,we],we}}, +{{wings_va,set_both_edge_attrs,3},{[int,val,val,we],we}}, +{{wings_va,set_edge_attrs,1},{[list,we],we}}, +{{wings_va,set_edge_attrs,3},{[int,atom,val,we],we}}, +{{wings_va,set_edge_color,2},{[gbset,vec3,we],we}}, +{{wings_va,set_edge_color,3},{[int,vec3,vec3,we],we}}, +{{wings_va,set_edge_colors,1},{[list,we],we}}, +{{wings_va,set_edge_uvs,1},{[list,we],we}}, +{{wings_va,set_face_attr_vs,3},{[atom,int,list,we],we}}, +{{wings_va,set_face_attrs,2},{[int,val,we],we}}, +{{wings_va,set_face_color,2},{[gbset,vec3,we],we}}, +{{wings_va,set_vertex_color,2},{[gbset,vec3,we],we}}, +{{wings_va,set_vtx_face_uvs,3},{[int,list,vec2,we],we}}, +{{wings_va,vtx_attrs,1},{[int,we],attr}}, +{{wings_va,vtx_attrs,2},{[int,int,we],attr}}, +{{wings_vertex,bounding_box,0},{[we],vec3}}, +{{wings_vertex,bounding_box,1},{[we,val],vec3}}, +{{wings_vertex,bounding_box,2},{[list,we,val],vec3}}, +{{wings_vertex,center,0},{[we],vec3}}, +{{wings_vertex,center,1},{[gbset,we],vec3}}, +{{wings_vertex,connect,2},{[int,list,we],we}}, +{{wings_vertex,connect_cut,2},{[int,int,we],[we,gbset]}}, +{{wings_vertex,dissolve_isolated,1},{[list,we],we}}, +{{wings_vertex,edge_through,2},{[int,int,we],int}}, +{{wings_vertex,edge_through,3},{[int,int,int,we],int}}, +{{wings_vertex,flatten,2},{[list,vec3,we],we}}, +{{wings_vertex,flatten,3},{[list,vec3,vec3,we],we}}, +{{wings_vertex,force_connect,3},{[int,int,int,we],we}}, +{{wings_vertex,from_edges,1},{[gbset,we],gbset}}, +{{wings_vertex,from_faces,1},{[gbset,we],list}}, +{{wings_vertex,isolated,0},{[we],gbset}}, +{{wings_vertex,normal,1},{[int,we],vec3}}, +{{wings_vertex,outer_vertices_ccw,1},{[list,we],list}}, +{{wings_vertex,per_face,1},{[list,we],list}}, +{{wings_vertex,pos,1},{[int,we],vec3}}, +{{wings_vertex,reachable,1},{[list,we],list}}, +{{wings_vertex_cmd,bevel_vertex,1},{[int,we],we}}, +{{wings_vertex_cmd,connect,1},{[list,we],we}}, +{{wings_we,all_hidden,0},{[we],bool}}, +{{wings_we,break_mirror,0},{[we],we}}, +{{wings_we,centroid,0},{[we],vec3}}, +{{wings_we,create_holes,1},{[list,we],we}}, +{{wings_we,create_mirror,1},{[int,we],we}}, +{{wings_we,fast_rebuild,0},{[we],we}}, +{{wings_we,freeze_mirror,0},{[we],we}}, +{{wings_we,fully_visible_edges,1},{[ordset,we],list}}, +{{wings_we,hide_faces,1},{[gbset,we],we}}, +{{wings_we,invert_normals,0},{[we],we}}, +{{wings_we,is_consistent,0},{[we],bool}}, +{{wings_we,is_face_consistent,1},{[int,we],bool}}, +{{wings_we,is_open,0},{[we],bool}}, +{{wings_we,mirror_projection,0},{[we],matrix}}, +{{wings_we,new_id,0},{[we],[int,we]}}, +{{wings_we,new_ids,1},{[int,we],[int,we]}}, +{{wings_we,new_wrap_range,2},{[int,int,we],[tuple,we]}}, +{{wings_we,normals,2},{[list,we,matrix],list}}, +{{wings_we,num_hidden,0},{[we],int}}, +{{wings_we,perimeter,0},{[we],float}}, +{{wings_we,rebuild,0},{[we],we}}, +{{wings_we,renumber,1},{[we,int],we}}, +{{wings_we,renumber,2},{[we,int,list],we}}, +{{wings_we,separate,0},{[we],list}}, +{{wings_we,show_faces,0},{[we],we}}, +{{wings_we,show_faces,1},{[list,we],we}}, +{{wings_we,surface_area,0},{[we],float}}, +{{wings_we,transform_vs,1},{[matrix,we],we}}, +{{wings_we,uv_mapped_faces,0},{[we],list}}, +{{wings_we,uv_to_color,0},{[we,st],we}}, +{{wings_we,validate_mirror,0},{[we],we}}, +{{wings_we,visible,0},{[we],list}}, +{{wings_we,visible,1},{[list,we],list}}, +{{wings_we,visible_edges,0},{[we],list}}, +{{wings_we,visible_edges,1},{[gbset,we],list}}, +{{wings_we,visible_vs,0},{[we],list}}, +{{wings_we,visible_vs,1},{[list,we],list}}, +{{wings_we,volume,0},{[we],float}}, +{{e3d_mat,identity,0},{[],matrix}}, +{{e3d_mat,is_identity,1},{[matrix],bool}}, +{{e3d_mat,determinant,1},{[matrix],float}}, +{{e3d_mat,print,1},{[matrix],atom}}, +{{e3d_mat,compress,1},{[matrix],matrix}}, +{{e3d_mat,expand,1},{[matrix],matrix}}, +{{e3d_mat,translate,1},{[vec3],matrix}}, +{{e3d_mat,translate,3},{[float,float,float],matrix}}, +{{e3d_mat,scale,1},{[vec3_or_float],matrix}}, +{{e3d_mat,scale,3},{[float,float,float],matrix}}, +{{e3d_mat,rotate,2},{[float,vec3],matrix}}, +{{e3d_mat,rotate_from_euler_rad,1},{[vec3],matrix}}, +{{e3d_mat,rotate_from_euler_rad,3},{[float,float,float],matrix}}, +{{e3d_mat,rotate_to_z,1},{[vec3],matrix}}, +{{e3d_mat,rotate_s_to_t,2},{[vec3,vec3],matrix}}, +{{e3d_mat,project_to_plane,1},{[vec3],matrix}}, +{{e3d_mat,transpose,1},{[matrix],matrix}}, +{{e3d_mat,invert,1},{[matrix],matrix}}, +{{e3d_mat,add,2},{[matrix,matrix],matrix}}, +{{e3d_mat,mul,1},{[list],matrix}}, +{{e3d_mat,mul,2},{[matrix,matrix_or_float],matrix}}, +{{e3d_mat,mul_point,2},{[matrix,vec3],vec3}}, +{{e3d_mat,mul_vector,2},{[matrix,vec3],vec3}}, +{{e3d_mat,eigenv3,1},{[matrix],[vec3,matrix]}}, +{{e3d_vec,zero,0},{[],vec3}}, +{{e3d_vec,is_zero,1},{[vec3],bool}}, +{{e3d_vec,add,1},{[list],vec3}}, +{{e3d_vec,add,2},{[vec3,vec3],vec3}}, +{{e3d_vec,add_prod,3},{[vec3,vec3,float],vec3}}, +{{e3d_vec,sub,1},{[list],vec3}}, +{{e3d_vec,sub,2},{[vec3,vec3],vec3}}, +{{e3d_vec,lerp,3},{[vec3,vec3,float],vec3}}, +{{e3d_vec,norm_sub,2},{[vec3,vec3],vec3}}, +{{e3d_vec,mul,2},{[vec3,float],vec3}}, +{{e3d_vec,divide,2},{[vec3,float],vec3}}, +{{e3d_vec,neg,1},{[vec3],vec3}}, +{{e3d_vec,dot,2},{[vec3,vec3],float}}, +{{e3d_vec,cross,2},{[vec3,vec3],vec3}}, +{{e3d_vec,len,1},{[vec3],float}}, +{{e3d_vec,len_sqr,1},{[vec3],float}}, +{{e3d_vec,dist,2},{[vec3,vec3],float}}, +{{e3d_vec,dist_sqr,2},{[vec3,vec3],float}}, +{{e3d_vec,norm,1},{[vec3],vec3}}, +{{e3d_vec,norm,3},{[float,float,float],vec3}}, +{{e3d_vec,normal,3},{[vec3,vec3,vec3],vec3}}, +{{e3d_vec,normal,1},{[list],vec3}}, +{{e3d_vec,average,1},{[list],vec3}}, +{{e3d_vec,average,2},{[vec3,vec3],vec3}}, +{{e3d_vec,average,4},{[vec3,vec3,vec3,vec3],vec3}}, +{{e3d_vec,area,3},{[vec3,vec3,vec3],float}}, +{{e3d_vec,degrees,2},{[vec3,vec3],float}}, +{{e3d_vec,plane,1},{[list],tuple}}, +{{e3d_vec,plane,2},{[vec3,vec3],tuple}}, +{{e3d_vec,plane,3},{[vec3,vec3,vec3],tuple}}, +{{e3d_vec,plane_side,2},{[vec3,tuple],integer}}, +{{e3d_vec,plane_dist,2},{[vec3,tuple],float}}, +{{e3d_vec,line_dist,3},{[vec3,vec3,vec3],float}}, +{{e3d_vec,line_dist_sqr,3},{[vec3,vec3,vec3],float}}, +{{e3d_vec,line_line_intersect,4},{[vec3,vec3,vec3,vec3],any}}, +{{e3d_vec,project_2d,2},{[vec3,atom],vec3}}, +{{e3d_vec,largest_dir,1},{[vec3],atom}}, +{{e3d_bv,box,0},{[],tuple}}, +{{e3d_bv,box,1},{[list],tuple}}, +{{e3d_bv,box,2},{[any,any],tuple}}, +{{e3d_bv,box,3},{[vec3,vec3,float],tuple}}, +{{e3d_bv,union,2},{[tuple,tuple],tuple}}, +{{e3d_bv,dist,2},{[tuple,tuple],float_or_false}}, +{{e3d_bv,intersect,2},{[tuple,tuple],bool}}, +{{e3d_bv,center,1},{[tuple],vec3}}, +{{e3d_bv,max_extent,1},{[tuple],any}}, +{{e3d_bv,surface_area,1},{[tuple],float}}, +{{e3d_bv,volume,1},{[tuple],float}}, +{{e3d_bv,sphere,1},{[tuple],tuple}}, +{{e3d_bv,inside,2},{[vec3,tuple],bool}}, +{{e3d_bv,hit,2},{[tuple,tuple],bool}}, +{{e3d_bv,hit,3},{[tuple,tuple,tuple],bool}}, +{{e3d_bv,inv_sign,1},{[vec3],tuple}}, +{{e3d_bv,eigen_vecs,1},{[list],tuple}}, +{{e3d_bv,quickhull,1},{[list],tuple}}, +{{e3d_bv,covariance_matrix,1},{[list],tuple}} +]. + diff --git a/plugins_src/scripting/init_scripts/defaults.conf b/plugins_src/scripting/init_scripts/defaults.conf new file mode 100644 index 000000000..a6b53fb61 --- /dev/null +++ b/plugins_src/scripting/init_scripts/defaults.conf @@ -0,0 +1,24 @@ + +%% +%% Scripting for Shapes +%% +%% Copyright 2025 Edward Blake +%% +%% See the file "license.terms" for information on usage and redistribution +%% of this file, and for a DISCLAIMER OF ALL WARRANTIES. +%% +%% $Id$ +%% + +%% +%% Set defaults before the user specific preferences are set. + +%% +%% To set the python binary filename, see python.script-init-conf +%% To set the gauche binary filename, see scheme.script-init-conf +%% + +{setting_enable_commands, true}. +{setting_enable_import, true}. +{setting_enable_export, true}. + diff --git a/plugins_src/scripting/init_scripts/py-modnames b/plugins_src/scripting/init_scripts/py-modnames new file mode 100644 index 000000000..5b83fec88 --- /dev/null +++ b/plugins_src/scripting/init_scripts/py-modnames @@ -0,0 +1,36 @@ + +%% +%% Scripting for Shapes +%% +%% Copyright 2025 Edward Blake +%% +%% See the file "license.terms" for information on usage and redistribution +%% of this file, and for a DISCLAIMER OF ALL WARRANTIES. +%% +%% $Id$ +%% + +%% How modules are named in the generated python script files. +[ +{wings_collapse, [collapse]}, +{wings_dissolve, [dissolve]}, +{wings_edge, [edge]}, +{wings_edge_cmd, [edge_cmd]}, +{wings_edge_loop, [edge_loop]}, +{wings_extrude_edge, [extrude_edge]}, +{wings_extrude_face, [extrude_face]}, +{wings_face, [face]}, +{wings_face_cmd, [face_cmd]}, +{wings_facemat, [facemat]}, +{wings_subdiv, [subdiv]}, +{wings_tesselation, [tesselation]}, +{wings_util, [util]}, +{wings_va, [va]}, +{wings_vertex, [vertex]}, +{wings_vertex_cmd, [vertex_cmd]}, +{wings_we, [we]}, +{e3d_mat, [e3d_mat]}, +{e3d_vec, [e3d_vec]}, +{e3d_bv, [e3d_bv]} +]. + diff --git a/plugins_src/scripting/init_scripts/py/init.py b/plugins_src/scripting/init_scripts/py/init.py new file mode 100644 index 000000000..1bab1fc8c --- /dev/null +++ b/plugins_src/scripting/init_scripts/py/init.py @@ -0,0 +1,128 @@ + +## +## Scripting for Shapes (Scheme and Python) +## +## Copyright 2023-2025 Edward Blake +## +## See the file "license.terms" for information on usage and redistribution +## of this file, and for a DISCLAIMER OF ALL WARRANTIES. +## +## $Id$ +## + +## +## This python code acts as a boot strap to define some useful functions +## and then load the actual script that is sent as a symbolic expression through +## standard input by the erlang plugin. +## + +import w3d_int +import sys +import time +from os import path +from runpy import run_path + + +sys.stdin.reconfigure(encoding="utf-8") +sys.stdout.reconfigure(encoding="utf-8") +sys.stderr.reconfigure(encoding="utf-8") + +scr_langstrs = {} + +def set_lang_strings(l): + global scr_langstrs + for a in l: + scr_langstrs[a[0]] = a[1] + +def __t(id, str): + global scr_langstrs + if id in scr_langstrs: + return scr_langstrs[id] + else: + return str + +def script_loader_loop(): + # Main function to call, can be called many times + scr_main_fun = None + while True: + while True: + line = sys.stdin.readline() + if (line == ""): + pass + cmd_0, _ = w3d_int.scm_parse(line) + cmd = cmd_0[0] + break + + cmd_atom = cmd[0] + if cmd_atom == "run_init": + g = {} + g["scm_parse"] = w3d_int.scm_parse + g["OutputList"] = w3d_int.OutputList + g["Okay"] = w3d_int.Okay + ## Use __t(20, "Text") for localized text + g["__t"] = __t + script_file = cmd[1] + set_lang_strings(cmd[2]) + sys.path.insert(0, path.dirname(script_file)) + g2 = run_path(script_file, g) + scr_main_fun = None + if "w3d_main_function" in g2: + scr_main_fun = g2["w3d_main_function"] + if scr_main_fun == None: + sys.stderr.write("ERROR: main function not set\n") + exit(1) + print("(ok)") + sys.stdout.flush() + + elif cmd_atom == "run": + ## Parameters being passed to the script from the script's + ## parameter options window. + params = [] + + ## Access parameters by key, for those that used an atom key + params_by_key = {} + + ## Extra parameters passed as the third argument + ## These are parameters that are not set via a parameter + ## window and may be auxiliary variables dependent on the + ## type of script (e.g. "content" contains an e3d_file tuple + ## for exporter plugins, parameters set with "script_params" + ## also show up in here). + extra_params = {} + + params = cmd[2] + + ## Add to params_by_key parameters that are keyed + for p in params: + if hasattr(p, "__len__"): + if len(p) == 2: + pv = p[1] + params_by_key[p[0]] = pv + + ## Grab the extra params + extra_params_1 = cmd[3] + for p in extra_params_1: + extra_params[p[0]] = p[1] + returned = scr_main_fun(params, params_by_key, extra_params) + if returned != None: + print("") + o = None + if hasattr(returned,"as_output_list"): + o = returned.as_output_list() + elif hasattr(returned,"__len__"): + o = w3d_int.OutputList() + for r in returned: + o.add_value(r) + if o != None: + o.write_list_out(sys.stdout) + print("") + sys.stdout.flush() + print("(%ok)") + sys.stdout.flush() + + else: + print("ERROR: Not run") + exit(1) + +script_loader_loop() + diff --git a/plugins_src/scripting/init_scripts/py/w3d_e3d.py b/plugins_src/scripting/init_scripts/py/w3d_e3d.py new file mode 100644 index 000000000..09468c753 --- /dev/null +++ b/plugins_src/scripting/init_scripts/py/w3d_e3d.py @@ -0,0 +1,538 @@ + +## +## Scripting for Shapes (Scheme and Python) +## +## Copyright 2023-2025 Edward Blake +## +## See the file "license.terms" for information on usage and redistribution +## of this file, and for a DISCLAIMER OF ALL WARRANTIES. +## +## $Id$ +## + +## +## Helper classes for Scripting to Wings 3D +## + +from w3d_int import OutputList +import sys + +E3D_INFINITY = 3.402823e+38 ## 32 bits float max + +def list_of_objects_to_outputlist(objlist): + if hasattr(objlist, "__len__"): + outputlist = OutputList() + for obj in objlist: + if type(obj) is str: + outputlist.add_str(obj) + else: + outputlist.add_value(obj) + return outputlist + else: + return objlist.as_output_list() + + +def list_of_pairs_to_outputlist(objlist): + if hasattr(objlist, "__len__"): + outputlist = OutputList() + for obj in objlist: + o = obj.as_output_list() + outputlist.add_vector(o) + return outputlist + else: + return objlist.as_output_list() + +def list_of_atoms_to_outputlist(objlist): + if hasattr(objlist, "__len__"): + outputlist = OutputList() + outputlist.add_symbol('!list') + for obj in objlist: + if type(obj) is str: + outputlist.add_symbol(obj) + else: + outputlist.add_value(obj) + return outputlist + else: + return objlist.as_output_list() + +## Types for transform +class E3DTransf: + def __init__(self): + self.mat = [] + self.inv = [] + + def send(self): + o3 = OutputList() + o3.add_symbol("e3d_transf") + o3.add_list(self.mat) + o3.add_list(self.inv) + return o3 + + def load_from(self, tuple): + if tuple[0] == "e3d_transf": + t_mats = tuple[1] + for mt in t_mats: + self.mat.append(mt) + t_invs = tuple[2] + for inv in t_invs: + self.inv.append(inv) + + +class Ray: + def __init__(self): + self.o = None + self.d = None + self.n = None + self.f = None + self.bfc = True + + def as_output_list(self): + o3 = OutputList() + o3.add_symbol("ray") + o3.add_vector(self.o) + o3.add_vector(self.d) + o3.add_float(self.n) + o3.add_float(self.f) + o3.add_boolean(self.bfc) + + return o3 + + def load_from(self, tuple): + if tuple[0] == "ray": + t_o = tuple[1] + self.o = t_o + t_d = tuple[2] + self.d = t_d + t_n = tuple[3] + self.n = t_n # Near, far (or MinT MaxT) + t_f = tuple[4] + self.f = t_f + self.bfc = tuple[5] + + +class E3DFace: + def __init__(self): + self.vs = [] #List of vertex indices. + self.vc = [] #Vertex color indices. + self.tx = [] #List of texture indices. + self.ns = [] #List of normal indices. + self.mat = [] #Materials for face. + self.sg = 1 #Smooth group for face. + self.vis = -1 #Visible edges (as in 3DS). + + def as_output_list(self): + o3 = OutputList() + o3.add_symbol("e3d_face") + + o3.add_list(list_of_objects_to_outputlist(self.vs)) + o3.add_list(list_of_objects_to_outputlist(self.vc)) + o3.add_list(list_of_objects_to_outputlist(self.tx)) + o3.add_list(list_of_objects_to_outputlist(self.ns)) + o3.add_list(list_of_atoms_to_outputlist(self.mat)) + + o3.add_integer(self.sg) + o3.add_integer(self.vis) + return o3 + + def load_from(self, tuple): + if tuple[0] == "e3d_face": + t_vs = tuple[1] + self.vs = [] + for vs1 in t_vs: + self.vs.append(vs1) + + t_vc = tuple[2] + self.vc = [] + for vc1 in t_vc: + self.vc.append(vc1) + + t_tx = tuple[3] + self.tx = [] + for tx1 in t_tx: + self.tx.append(tx1) + + t_ns = tuple[4] + self.ns = [] + for ns1 in t_ns: + self.ns.append(ns1) + + t_mats = tuple[5] + self.mat = [] #Materials for face. + for mat1 in t_mats: + self.mat.append(mat1) + self.sg = tuple[6] + self.vis = tuple[7] + +## Polygon mesh. +class E3DMesh: + def __init__(self): + self.type = "triangle" # 'triangle' | 'quad' | 'polygon', + self.vs = [] #Vertex table (list). + self.vc = [] #Vertex color table (list). + self.tx = [] #Texture coordinates (list). + self.ns = [] #Normal table (list). + self.fs = [] #Face table (list of e3d_face). + self.he = [] #List of chains of hard edges. + self.matrix = "identity" #Local coordinate system. + + def as_output_list(self): + o3 = OutputList() + o3.add_symbol("e3d_mesh") + o3.add_symbol(self.type) + + o3.add_list(list_of_objects_to_outputlist(self.vs)) + o3.add_list(list_of_objects_to_outputlist(self.vc)) + o3.add_list(list_of_objects_to_outputlist(self.tx)) + o3.add_list(list_of_objects_to_outputlist(self.ns)) + o3.add_list(list_of_objects_to_outputlist(self.fs)) + o3.add_list(list_of_objects_to_outputlist(self.he)) + + if self.matrix == 'identity': + o3.add_symbol(self.matrix) + else: + o3.add_vector(list_of_objects_to_outputlist(self.matrix)) + + return o3 + + def load_from(self, tuple): + if tuple[0] == "e3d_mesh": + self.type = tuple[1] + + t_vs = tuple[2] + self.vs = [] + for vs1 in t_vs: + self.vs.append((vs1[0], vs1[1], vs1[2])) + + t_vc = tuple[3] + self.vc = [] + for vc1 in t_vc: + self.vc.append(vc1) + + t_tx = tuple[4] + self.tx = [] + for tx1 in t_tx: + self.tx.append(tx1) + + t_ns = tuple[5] + self.ns = [] + for ns1 in t_ns: + self.ns.append(ns1) + + t_fs = tuple[6] + self.fs = [] + for fs1 in t_fs: + new_fs = E3DFace() + new_fs.load_from(fs1) + self.fs.append(new_fs) + + t_he = tuple[7] + self.he = [] + for he1 in t_he: + self.he.append(he1) + + self.matrix = tuple[8] + +class E3DObject: + def __init__(self): + self.name = None #Name of object (string), or 'undefined' if no name. + self.obj = None #Object implementation. + self.mat = [] #Materials for this object. + self.attr = [] #List of attributes. + + def as_output_list(self): + o3 = OutputList() + o3.add_symbol("e3d_object") + if self.name is None: + o3.add_symbol("undefined") + else: + o3.add_str(self.name) + + if self.obj is None: + o3.add_symbol("undefined") + else: + o_obj = self.obj.as_output_list() + o3.add_list(o_obj) + + o3.add_list(list_of_pairs_to_outputlist(self.mat)) + o3.add_list(list_of_pairs_to_outputlist(self.attr)) + return o3 + + def load_from(self, tuple): + if tuple[0] == 'e3d_object': + t_name_0 = tuple[1] + if t_name_0 != 'undefined': + t_name = str(t_name_0) + if t_name != '': + self.name = t_name + t_obj = tuple[2] + self.obj = None + if t_obj != 'undefined' and hasattr(t_obj, "__len__"): + new_obj = E3DMesh() + new_obj.load_from(t_obj) + self.obj = new_obj + + t_mats = tuple[3] + for t_mat in t_mats: + pass ## TODO + + t_attrs = tuple[4] + for t_attr in t_attrs: + pass ## TODO + +class E3DImage: + def __init__(self): + self.type = 'r8g8b8' ## [g8 (gray8), a8 (alpha8) (Ch:Size)+[s|f]=signed|float] + self.bytes_pp = 3 ## bytes per pixel + self.alignment = 1 ## A = 1|2|4 Next row starts direct|even 2|even 4 + self.order = 'lower_left' ## First pixel is in: lower_left,lower_right,upper_left,upper_right] + self.width = 0 ## in pixels + self.height = 0 ## in pixels + self.image = None ## This is a temporary file path to a raw image + self.filename = None ## Filename or none + self.name = "" ## Name of image + self.extra = [] + + def as_output_list(self): + o3 = OutputList() + o3.add_symbol('e3d_image') + o3.add_symbol(self.type) + o3.add_integer(self.bytes_pp) + o3.add_integer(self.alignment) + o3.add_symbol(self.order) + o3.add_integer(self.width) + o3.add_integer(self.height) + + if self.image == None: + o3.add_symbol('none') + else: + o3.add_str(self.image) ## Temporary file for raw images + + if self.filename == None: + o3.add_symbol('none') + else: + o3.add_str(self.filename) + o3.add_str(self.name) + o3.add_list(list_of_objects_to_outputlist(self.extra)) + + return o3 + + def load_from(self, tuple): + if tuple[0] == 'e3d_image': + self.type = tuple[1] + self.bytes_pp = tuple[2] + self.alignment = tuple[3] + self.order = tuple[4] + self.width = tuple[5] + self.height = tuple[6] + + ## TODO: Grab the binary raw image from the temporary file + self.image = tuple[7] ## Temporary file to raw image + + self.filename = tuple[8] + self.name = tuple[9] + self.extra = tuple[10] + +class MaterialMaps: + def __init__(self): + self.maps = {} + + def as_output_list(self): + o3 = OutputList() + + for k in self.maps.keys(): + v = self.maps[k] + attr_item = OutputList() + attr_item.add_symbol(k) + attr_item.add_vector(v.as_output_list()) + o3.add_vector(attr_item) + return o3 + + def load_from(self, tlist): + for t_m in tlist: + if t_m[0] == 'diffuse': + new_img = E3DImage() + new_img.load_from(t_m[1]) + self.maps[t_m[0]] = new_img + + +class MaterialOpenGLAttributes: + def __init__(self): + self.ambient = [0.0,0.0,0.0,0.0] + self.specular = [0.1,0.1,0.1,1.0] + self.shininess = 0.2 + self.diffuse = [0.7,0.7,0.7,1.0] + self.emission = [0.0,0.0,0.0,1.0] + self.metallic = 0.1 + self.roughness = 0.8 + self.vertex_colors = 'set' + + def as_output_list(self): + + o3 = OutputList() + + ov_r = OutputList() + ov_r.add_numbers(self.ambient) + ov = OutputList() + ov.add_symbol('ambient') + ov.add_vector(ov_r) + o3.add_vector(ov) + + ov_r = OutputList() + ov_r.add_numbers(self.specular) + ov = OutputList() + ov.add_symbol('specular') + ov.add_vector(ov_r) + o3.add_vector(ov) + + ov = OutputList() + ov.add_symbol('shininess') + ov.add_float(self.shininess) + o3.add_vector(ov) + + ov_r = OutputList() + ov_r.add_numbers(self.diffuse) + ov = OutputList() + ov.add_symbol('diffuse') + ov.add_vector(ov_r) + o3.add_vector(ov) + + ov_r = OutputList() + ov_r.add_numbers(self.emission) + ov = OutputList() + ov.add_symbol('emission') + ov.add_vector(ov_r) + o3.add_vector(ov) + + ov = OutputList() + ov.add_symbol('metallic') + ov.add_float(self.metallic) + o3.add_vector(ov) + + ov = OutputList() + ov.add_symbol('roughness') + ov.add_float(self.roughness) + o3.add_vector(ov) + + ov = OutputList() + ov.add_symbol('vertex_colors') + ov.add_symbol(self.vertex_colors) + o3.add_vector(ov) + + return o3 + + def load_from(self, tlist): + for t_at in tlist: + if t_at[0] == 'ambient': + self.ambient = t_at[1] + if t_at[0] == 'specular': + self.specular = t_at[1] + if t_at[0] == 'shininess': + self.shininess = t_at[1] + if t_at[0] == 'diffuse': + self.diffuse = t_at[1] + if t_at[0] == 'emission': + self.emission = t_at[1] + if t_at[0] == 'metallic': + self.metallic = t_at[1] + if t_at[0] == 'roughness': + self.roughness = t_at[1] + if t_at[0] == 'vertex_colors': + self.vertex_colors = t_at[1] + + +class Material: + def __init__(self): + self.name = "material" + self.attrs = {} + + def as_output_list(self): + o3 = OutputList() + o3.add_symbol(self.name) + + attr_list = OutputList() + for k in self.attrs.keys(): + v = self.attrs[k] + attr_item = OutputList() + attr_item.add_symbol(k) + attr_item.add_list(v.as_output_list()) + attr_list.add_vector(attr_item) + o3.add_list(attr_list) + return o3 + + def load_from(self, tuple): + self.name = tuple[0] + t_attrs = tuple[1] + for t_att in t_attrs: + k = t_att[0] + if k == 'maps': + new_attr = MaterialMaps() + new_attr.load_from(t_att[1]) + elif k == 'opengl': + new_attr = MaterialOpenGLAttributes() + new_attr.load_from(t_att[1]) + else: + new_attr = t_att[1] + self.attrs[k] = new_attr + + +class E3DFile: + def __init__(self): + self.objs = [] # List of objects. + self.mat = [] # List of materials. + self.creator = "" # Creator string. + self.dir = "" # Directory for file. + + def as_output_list(self): + o3 = OutputList() + o3.add_symbol("e3d_file") + + o3.add_list(list_of_objects_to_outputlist(self.objs)) + o3.add_list(list_of_pairs_to_outputlist(self.mat)) + + o3.add_str(self.creator) + o3.add_str(self.dir) + return o3 + + def load_from(self, tuple): + if tuple[0] == 'e3d_file': + t_objects = tuple[1] + self.objs = [] + for t_object in t_objects: + new_obj = E3DObject() + new_obj.load_from(t_object) + self.objs.append(new_obj) + + t_mats = tuple[2] + self.mat = [] + for t_mat in t_mats: + new_mat = Material() + new_mat.load_from(t_mat) + self.mat.append(new_mat) + + self.creator = str(tuple[3]) + + t_dir = tuple[4] + if t_dir == 'undefined': + self.dir = None + else: + self.dir = str(t_dir) + + +## For scripts that change an E3DMesh and return the results +class SetE3DMesh: + def __init__(self,mesh): + self.mesh = mesh + def as_output_list(self): + o = OutputList() + o.add_symbol("set_e3d_mesh") + o.add_list(self.mesh.as_output_list()) + return o + + +def test(): + b = E3DFile() + b.objs.append(E3DObject()) + o = b.as_output_list() + o.write_list_out(sys.stdout) + + diff --git a/plugins_src/scripting/init_scripts/py/w3d_int.py b/plugins_src/scripting/init_scripts/py/w3d_int.py new file mode 100644 index 000000000..06f349317 --- /dev/null +++ b/plugins_src/scripting/init_scripts/py/w3d_int.py @@ -0,0 +1,421 @@ + +## +## Scripting for Shapes (Scheme and Python) +## +## Copyright 2023-2025 Edward Blake +## +## See the file "license.terms" for information on usage and redistribution +## of this file, and for a DISCLAIMER OF ALL WARRANTIES. +## +## $Id$ +## + +## +## Symbolic expression reader and writer for script input/output +## +## Python script side +## + +import sys +import threading +import time + +def t_1(): + scm_parse("(test '(0 1 2 3) #(2 3 4 5) '(\"Test\" 2 #f))") + +def scm_parse(a): + inp = a + lst = [] + tok = "" + while inp != "": + if inp == "" and tok == "": + return lst, "" + elif inp == "" and len(tok) > 0: + lst.append(bare_word(tok)) + tok = "" + elif inp[0] == '\"': + r = inp[1:] + str1, r_0 = scm_parse_str(r) + inp = r_0 + lst.append(str1) + elif inp[0] == '#' and inp[1] == '(': + if tok == "": + r = inp[2:] + sublist, r_0 = scm_parse(r) + lst.append(sublist) + inp = r_0 + else: + lst.append(bare_word(tok)) + tok = "" + elif inp[0] == '\'' and inp[1] == '(': + if tok == "": + r = inp[2:] + sublist, r_0 = scm_parse(r) + lst.append(sublist) + inp = r_0 + else: + lst.append(bare_word(tok)) + tok = "" + elif inp[0] == '(': + if tok == "": + sublist, r_0 = scm_parse(inp[1:]) + inp = r_0 + lst.append(sublist) + else: + lst.append(bare_word(tok)) + tok = "" + elif inp[0] == ')': + if tok == "": + return lst, inp[1:] + else: + lst.append(bare_word(tok)) + tok = "" + elif inp[0] == ' ' or inp[0] == '\t' or inp[0] == '\r' or inp[0] == '\n': + if tok == "": + inp = inp[1:] + tok = "" + else: + lst.append(bare_word(tok)) + tok = "" + elif ((inp[0] >= '0' and inp[0] <= '9') or (inp[0] == '.') or (inp[0] == '-')) and tok == "": + num, r_1 = scm_parse_num(inp) + lst.append(num) + tok = "" + inp = r_1 + else: + tok = tok + inp[0] + inp = inp[1:] + return lst, "" + + +def scm_parse_num(a): + inp = a + str1 = "" + is_float = False + while inp != "": + if inp[0] == ' ' or inp[0] == ')' or inp[0] == '\r' or inp[0] == '\n': + if is_float: + num = float(str1) + else: + num = int(str1) + return num, inp + else: + if inp[0] == '.': + is_float = True + str1 = str1 + inp[0] + inp = inp[1:] + if is_float: + num = float(str1) + else: + num = int(str1) + return num, "" + +def scm_parse_str(a): + inp = a + str1 = "" + while inp != "": + if inp[0] == '\\': + str1 = str1 + inp[1] + inp = inp[2:] + elif inp[0] == '"': + return str1, inp[1:] + else: + str1 = str1 + inp[0] + inp = inp[1:] + +def bare_word(a0): + a1 = a0 + if a1 == "#t": + return True + elif a1 == "#f": + return False + else: + return a1 + +def send_get_reply(b): + print("") + b.write_list_out(sys.stdout) + print("") + sys.stdout.flush() + reply = None + while True: + line = sys.stdin.readline() + if (line == ""): + pass + reply_0, _ = scm_parse(line) + reply = reply_0[0] + break + return reply + + +def wings_set_var(varname, varval): + b = OutputList() + b.add_symbol("%setvar") + b.add_str(varname) + b.add_value(varval) + return send_get_reply(b) + +def wings_get_var(varname): + b = OutputList() + b.add_symbol("%getvar") + b.add_str(varname) + return send_get_reply(b) + +def wings_query(str): + b = OutputList() + b.add_symbol("%query") + b.add_str(str) + return send_get_reply(b) + + +def wings_we__get(module,fun,*args): + b = OutputList() + b.add_symbol("%we") + lt = OutputList() + lt.add_symbol(module) + lt.add_symbol(fun) + for arg in args: + lt.add_value(arg) + b.add_list(lt) + return send_get_reply(b) + + +def wings_we__change(module,fun,*args): + b = OutputList() + b.add_symbol("%we!") + lt = OutputList() + lt.add_symbol(module) + lt.add_symbol(fun) + for arg in args: + lt.add_value(arg) + b.add_list(lt) + return send_get_reply(b) + + +## TODO: wings_with_we_change Mod Name Args Fun + + +def wings_previous_we__get(stackindex,module,fun,*args): + b = OutputList() + b.add_symbol("%we-previous") + b.add_value(stackindex) + b.add_symbol(module) + b.add_symbol(fun) + for arg in args: + b.add_value(arg) + return send_get_reply(b) + + +def wings_previous_we__change(stackindex,module,fun,*args): + b = OutputList() + b.add_symbol("%we-previous!") + b.add_value(stackindex) + b.add_symbol(module) + b.add_symbol(fun) + for arg in args: + b.add_value(arg) + return send_get_reply(b) + + + +def list_of_objects_to_outputlist(objlist): + if hasattr(objlist, "__len__"): + outputlist = OutputList() + for obj in objlist: + if type(obj) is str: + outputlist.add_str(obj) + else: + outputlist.add_value(obj) + return outputlist + else: + return objlist.as_output_list() + +def list_of_pairs_to_outputlist(objlist): + if hasattr(objlist, "__len__"): + outputlist = OutputList() + for obj in objlist: + o = obj.as_output_list() + outputlist.add_vector(o) + return outputlist + else: + return objlist.as_output_list() + +class OutputList: + def __init__(self): + self.lst_cont = [] + self.lst_type = [] + + def add_symbol(self, a): + self.add(a, 0) + + def add_str(self, a): + self.add(a, 1) + + def add_number(self, a): + self.add(a, 2) + + def add_numbers(self, alst): + for a in alst: + self.add(a, 2) + + def add_integer(self, a): + self.add(a, 2) + + def add_integers(self, alst): + for a in alst: + self.add(a, 2) + + def add_float(self, a): + self.add(a, 2) + + def add_floats(self, alst): + for a in alst: + self.add(a, 2) + + def add_list(self, a): + self.add(a, 3) + + def add_vector(self, a): + self.add(a, 4) + + def add(self, a, typ): + self.lst_cont.append(a) + self.lst_type.append(typ) + + def add_value(self, obj): + if type(obj) is str: + self.add_str(obj) + elif type(obj) is tuple: + o = list_of_objects_to_outputlist(obj) + self.add_vector(o) + elif hasattr(obj, "__len__"): + o = list_of_objects_to_outputlist(obj) + self.add_list(o) + elif (type(obj) is int): + self.add_integer(obj) + elif type(obj) is float: + self.add_float(obj) + elif hasattr(obj, "as_output_list"): + o = obj.as_output_list() + self.add_list(o) + else: + o = obj.as_output_list() + self.add_list(o) + + def write_list_out(self, ost): + ost.write("(") + for i in range(0, len(self.lst_cont)): + if i > 0: + ost.write(" ") + if self.lst_type[i] == 0: + ost.write(self.lst_cont[i]) + elif self.lst_type[i] == 1: + ts = self.lst_cont[i] + ts = ts.replace("\\", "\\\\") + ts = ts.replace("\"", "\\\"") + ost.write("\"" + ts + "\"") + elif self.lst_type[i] == 2: + ost.write(str(self.lst_cont[i])) + elif self.lst_type[i] == 3: + self.lst_cont[i].write_list_out(ost) + elif self.lst_type[i] == 4: + ost.write("#") + self.lst_cont[i].write_list_out(ost) + ost.write(")") + +class Okay: + def __init__(self,*args): + self.args = args + def as_output_list(self): + o = OutputList() + o.add_symbol("ok") + for arg in self.args: + o.add_list(arg.as_output_list()) + return o + + +## For scripts that change vertex positions +class Points: + def __init__(self): + self.list = [] + + def as_output_list(self): + o3 = OutputList() + for v in self.list: + vo2 = OutputList() + vo3 = OutputList() + vo3.add_float(v[1][0]) + vo3.add_float(v[1][1]) + vo3.add_float(v[1][2]) + vo2.add_integer(v[0]) + vo2.add_vector(vo3) + o3.add_vector(vo2) + return o3 + + def load_from(self, vlist): + for pair in vlist: + a1 = pair[0] + a2 = pair[1] + a2_x = a2[0] + a2_y = a2[1] + a2_z = a2[2] + self.list.append((a1, (a2_x, a2_y, a2_z))) + +class SetPoints: + def __init__(self,points): + self.points = points + def as_output_list(self): + o = OutputList() + o.add_symbol("set_points") + o.add_value(self.points) + return o + + +## Long running process +## +## Example: +## +## class Examp: +## def run(self): +## sum = 0 +## for i in range(0, 100000000): +## sum = sum + i +## +## print("Begin") +## lrp = LongRunningProcess() +## lrp.run(Examp()) +## print("Done") +## + +def lrp_standby_function(lro): + while True: + lro.standbySema.acquire() + v = lro.standbyState + lro.standbySema.release() + if v: + break + print("('%keepalive 0)") + time.sleep(1) + +## A thread that simply sends keep-alive messages back to the scripting +## plugin to keep it from timing out while the script processes something +## that takes a while. +## +class LongRunningProcess: + def __init__(self): + self.standbyState = False + self.standbySema = threading.Semaphore() + self.standbyThread = threading.Thread(target=lrp_standby_function, args=(self,)) + + def run(self, runo): + self.standbyThread.start() + ## Try is needed or main thread will close on an error but leaves + ## the standby thread running. + try: + runo.run() + finally: + self.standbySema.acquire() + self.standbyState = True + self.standbySema.release() + self.standbyThread.join() + diff --git a/plugins_src/scripting/init_scripts/py/w3d_newshape.py b/plugins_src/scripting/init_scripts/py/w3d_newshape.py new file mode 100644 index 000000000..a1855c01c --- /dev/null +++ b/plugins_src/scripting/init_scripts/py/w3d_newshape.py @@ -0,0 +1,60 @@ + +## +## Scripting for Shapes (Scheme and Python) +## +## Copyright 2023-2025 Edward Blake +## +## See the file "license.terms" for information on usage and redistribution +## of this file, and for a DISCLAIMER OF ALL WARRANTIES. +## +## $Id$ +## + +from w3d_int import OutputList, list_of_pairs_to_outputlist +import sys + +class ListOfArrays: + def __init__(self, l): + self.l = l + def as_output_list(self): + l2 = OutputList() + for item in self.l: + l3 = OutputList() + l3.add_numbers(item) + l2.add_list(l3) + return l2 + +class ListOfTuples: + def __init__(self, l): + self.l = l + def as_output_list(self): + l2 = OutputList() + for item in self.l: + l3 = OutputList() + l3.add_numbers(item) + l2.add_vector(l3) + return l2 + +class NewShape: + def __init__(self): + self.prefix = "shape" + ## Face and Vertices version + self.fs = [] + self.vs = [] + + ## E3DObject version + self.obj = None + self.mat = [] + + def as_output_list(self): + o3 = OutputList() + o3.add_symbol("new_shape") + o3.add_str(self.prefix) + if self.obj == None: + o3.add_list(list_of_objects_to_outputlist(self.fs)) + o3.add_list(list_of_objects_to_outputlist(self.vs)) + else: + o3.add_list(self.obj.as_output_list()) + o3.add_list(list_of_pairs_to_outputlist(self.mat)) + return o3 + diff --git a/plugins_src/scripting/init_scripts/py/w3d_we.1.py b/plugins_src/scripting/init_scripts/py/w3d_we.1.py new file mode 100644 index 000000000..cc3fee182 --- /dev/null +++ b/plugins_src/scripting/init_scripts/py/w3d_we.1.py @@ -0,0 +1,17 @@ + +## +## Scripting for Shapes (Scheme and Python) +## +## Copyright 2025 Edward Blake +## +## See the file "license.terms" for information on usage and redistribution +## of this file, and for a DISCLAIMER OF ALL WARRANTIES. +## +## $Id$ +## + +from w3d_int import OutputList,wings_we__change,wings_we__get + +## This file is automatically generated from callable.conf and py-modnames + + diff --git a/plugins_src/scripting/init_scripts/python.script-init-conf b/plugins_src/scripting/init_scripts/python.script-init-conf new file mode 100644 index 000000000..fd95bfbec --- /dev/null +++ b/plugins_src/scripting/init_scripts/python.script-init-conf @@ -0,0 +1,29 @@ + +%% +%% Scripting for Shapes +%% +%% Copyright 2025 Edward Blake +%% +%% See the file "license.terms" for information on usage and redistribution +%% of this file, and for a DISCLAIMER OF ALL WARRANTIES. +%% +%% $Id$ +%% + +%% +%% Sets the default python binary filenames +%% + +%% NAME ARGS INITIAL SCRIPT +{ "py", false, "py/init.py", [ + %% List of interpreter filenames to try + "python", + "python38", + "python36", + "python34", + "python32", + "python3" +], [ + %% No special arguments +]}. + diff --git a/plugins_src/scripting/init_scripts/scheme.script-init-conf b/plugins_src/scripting/init_scripts/scheme.script-init-conf new file mode 100644 index 000000000..d23377201 --- /dev/null +++ b/plugins_src/scripting/init_scripts/scheme.script-init-conf @@ -0,0 +1,38 @@ + +%% +%% Scripting for Shapes +%% +%% Copyright 2025 Edward Blake +%% +%% See the file "license.terms" for information on usage and redistribution +%% of this file, and for a DISCLAIMER OF ALL WARRANTIES. +%% +%% $Id$ +%% + +%% +%% Sets the default gauche binary filenames +%% + +%% NAME ARGS INITIAL SCRIPT +{ "scm", true, "scm/init.scm", [ + %% List of interpreter filenames to try + "gosh", + "csi" +], [ + %% Special arguments depending on the interpreter found + {auto_fill, [ + %% Required arguments for Chicken Scheme interpreter (csi) + {"csi", [ + "-q", "-n", "-b", + "-eval", {wrap, "(load \"%s\")", {esc, {absname, "init_env_csi.scm", "%BASEDIR%/scm"}}} + ]}, + %% Required arguments for Gauche shell (gosh) + {"gosh", [ + "-q", "-b", + {concat, "-A", "%BASEDIR%/scm"}, + {concat, "-l", "init_env_gauche.scm"} + ]} + ]} +]}. + diff --git a/plugins_src/scripting/init_scripts/scm-modnames b/plugins_src/scripting/init_scripts/scm-modnames new file mode 100644 index 000000000..864a233ab --- /dev/null +++ b/plugins_src/scripting/init_scripts/scm-modnames @@ -0,0 +1,36 @@ + +%% +%% Scripting for Shapes +%% +%% Copyright 2025 Edward Blake +%% +%% See the file "license.terms" for information on usage and redistribution +%% of this file, and for a DISCLAIMER OF ALL WARRANTIES. +%% +%% $Id$ +%% + +%% How modules are named in the generated python script files. +[ +{wings_collapse, [we,collapse]}, +{wings_dissolve, [we,dissolve]}, +{wings_edge, [we,edge]}, +{wings_edge_cmd, [we,edge_cmd]}, +{wings_edge_loop, [we,edge_loop]}, +{wings_extrude_edge, [we,extrude_edge]}, +{wings_extrude_face, [we,extrude_face]}, +{wings_face, [we,face]}, +{wings_face_cmd, [we,face_cmd]}, +{wings_facemat, [we,facemat]}, +{wings_subdiv, [we,subdiv]}, +{wings_tesselation, [we,tesselation]}, +{wings_util, [we,util]}, +{wings_va, [we,va]}, +{wings_vertex, [we,vertex]}, +{wings_vertex_cmd, [we,vertex_cmd]}, +{wings_we, [we,we]}, +{e3d_mat, [e3d_mat]}, +{e3d_vec, [e3d_vec]}, +{e3d_bv, [e3d_bv]} +]. + diff --git a/plugins_src/scripting/init_scripts/scm/init.1.scm b/plugins_src/scripting/init_scripts/scm/init.1.scm new file mode 100644 index 000000000..0fda64e4f --- /dev/null +++ b/plugins_src/scripting/init_scripts/scm/init.1.scm @@ -0,0 +1,361 @@ + +;; +;; Scripting for Shapes (init for Scheme) +;; +;; Copyright 2023-2025 Edward Blake +;; +;; See the file "license.terms" for information on usage and redistribution +;; of this file, and for a DISCLAIMER OF ALL WARRANTIES. +;; +;; $Id$ +;; + +;; +;; This scheme code acts as a boot strap to define some useful functions +;; and then load the actual script. The actual script is sent by the erlang +;; plugin as a symbolic expression through standard input. +;; + +;; Main function of script, can be called multiple times +;; +(define *scr-main-fun* #f) +(define (main-function a) + (set! *scr-main-fun* a)) + +;; The full path of the script being run +(define *script-full-path* "") + +;; The directory of the script being run, the script +;; might want to use this to load it's other files. +(define *script-directory* "") + +;; Parameters being passed to the script from the script's +;; parameter options window. +(define *params* '()) + +;; Extra parameters passed as the third argument +;; These are parameters that are not set via a parameter +;; window and may be auxiliary variables dependent on the +;; type of script (e.g. "content" contains an e3d_file tuple +;; for exporter plugins, parameters set with "script_params" +;; also show up in here). +(define *extra-params* '()) + + +(define **scr-langstrs '()) + +(define (set-lang-string List) + (set! **scr-langstrs + (map (lambda (A) + (cons (vector-ref A 0) + (vector-ref A 1)) ) + List)) + ) + + +(define (?__ Id Str) + (define Found (assq Id **scr-langstrs)) + (if (eq? Found #f) + Str + (cdr Found)) + ) + + + +;; Long running process +;; +;; Example: +;; +;; (display "Started")(newline) +;; (long-running-process +;; (lambda () +;; (let loop ((N 100000000)) +;; (if (> N 0) +;; (loop (- N 1)) +;; 'ok)) +;; )) +;; (display "Done")(newline) +;; + +(define (long-running-process Fun) + ;; A thread that simply sends keep-alive messages back to the scripting + ;; plugin to keep it from timing out while the script processes something + ;; that takes a while. + ;; + (define StandbyState #f) + (define StandbySema (make-semaphore 1)) + (define StandbyThread + (make-thread (^[] (guard (e [else (report-error e) #f]) + (define (get_val) + (semaphore-acquire! StandbySema) + (let ((Val (if StandbyState #t #f))) + (semaphore-release! StandbySema) + Val)) + (let loop () + (if (get_val) + 'done + (let () + ;; The standard output seems to have some sort of buffering + ;; that prevents it from sending from this thread, current + ;; workaround is sending to standard error to bypass the buffering + (**send-back-list-ep (list '%keepalive 0)) + (thread-sleep! 1) + (loop)))) + )))) + + (thread-start! StandbyThread) + (unwind-protect (Fun) + (semaphore-acquire! StandbySema) + (set! StandbyState #t) + (semaphore-release! StandbySema) + (thread-join! StandbyThread) + ) + ) + + +(define (script-loop) + (define cmd (read)) + (case (list-ref cmd 0) + ((run_init) + (begin + (set! *script-full-path* (list-ref cmd 1)) + (set! *script-directory* (**loader-get-script-directory *script-full-path*)) + (**add-to-load-path *script-directory*) + (set-lang-string (list-ref cmd 2)) + (begin (load *script-full-path*)) + (if (equal? #f *scr-main-fun*) + (begin + (display "ERROR: main function not set" (current-error-port)) + (newline (current-error-port)) + (exit)) + (begin + (**send-back-list'(ok)) + (script-loop)))) + ) + ((run) + (begin + (let ((params (list-ref cmd 2)) + (extra-params (list-ref cmd 3))) + (define returned (*scr-main-fun* params extra-params)) + (if (not (undefined? returned)) + (begin + (newline) + (write returned) + ) + (begin + ) + ) + ) + (newline) + (**flush-out) + (**send-back-list '(%ok)) + (script-loop)) + ) + (else + (begin + (display "Not run") + (newline) + (exit)))) + ) + + +;; Used by the constructors for E3D objects +;; +(define (**loader-construct-from sc default names) + (define s-name (car default)) + (define s-defaults (cdr default)) + (cons s-name + (let loop ((sn names) (sd s-defaults)) + (if (equal? '() sn) + '() + (let ((res (assq (car sn) sc))) + (if res + (cons (list-ref res 1) (loop (cdr sn) (cdr sd))) + (cons (car sd) (loop (cdr sn) (cdr sd)))))) + ) + ) + ) + +;; Useful functions for e3d records +(define (e3d_transf? l) (equal? 'e3d_transf (list-ref l 0))) +(define (e3d_transf-mat l) (list-ref l 1)) ; +(define (e3d_transf-inv l) (list-ref l 2)) ; +(define (make-e3d_transf . sc) + (define default (list 'e3d_transf '() '())) + (define names (list 'mat 'inv)) + (**loader-construct-from sc default names) + ) + +(define (ray? l) (equal? 'ray (list-ref l 0))) +(define (ray-o l) (list-ref l 1)) ; +(define (ray-d l) (list-ref l 2)) ; +(define (ray-n l) (list-ref l 3)) ; Near, far (or MinT MaxT) +(define (ray-f l) (list-ref l 4)) ; +(define (ray-bfc l) (list-ref l 5)) ; Backface culling? +(define (make-ray . sc) + (define default (list 'ray #(0 0) #(0 0) 0.0 1.0 #t)) + (define names (list 'o 'd 'n 'f 'bfc)) + (**loader-construct-from sc default names) + ) + +(define (e3d_face? l) (equal? 'e3d_face (list-ref l 0))) +(define (e3d_face-vs l) (list-ref l 1)) ; List of vertex indices. +(define (e3d_face-vc l) (list-ref l 2)) ; Vertex color indices. +(define (e3d_face-tx l) (list-ref l 3)) ; List of texture indices. +(define (e3d_face-ns l) (list-ref l 4)) ; List of normal indices. +(define (e3d_face-mat l) (list-ref l 5)) ; Materials for face. +(define (e3d_face-sg l) (list-ref l 6)) ; Smooth group for face. +(define (e3d_face-vis l) (list-ref l 7)) ; Visible edges (as in 3DS). +(define (make-e3d_face . sc) + (define default (list 'e3d_face '() '() '() '() '() 1 -1)) + (define names (list 'vs 'vc 'tx 'ns 'mat 'sg 'vis)) + (define sc_1 (map + (lambda (b) + (if (eq? (car b) 'mat) + ;; Ensure that mat stays a list of atoms + (let ((matlist (list-ref b 1))) + (list (car b) (cons "!list" matlist))) + b + ) + ) sc)) + (**loader-construct-from sc_1 default names) + ) + +(define (e3d_mesh? l) (equal? 'e3d_mesh (list-ref l 0))) +(define (e3d_mesh-type l) (list-ref l 1)) ; 'triangle | 'quad | 'polygon +(define (e3d_mesh-vs l) (list-ref l 2)) ; Vertex table (list). +(define (e3d_mesh-vc l) (list-ref l 3)) ; Vertex color table (list). +(define (e3d_mesh-tx l) (list-ref l 4)) ; Texture coordinates (list). +(define (e3d_mesh-ns l) (list-ref l 5)) ; Normal table (list). +(define (e3d_mesh-fs l) (list-ref l 6)) ; Face table (list of e3d_face). +(define (e3d_mesh-he l) (list-ref l 7)) ; List of chains of hard edges. +(define (e3d_mesh-matrix l) (list-ref l 8)) ; Local coordinate system. +(define (make-e3d_mesh . sc) + (define default (list 'e3d_mesh 'poly '() '() '() '() '() '() 'identity)) + (define names (list 'type 'vs 'vc 'tx 'ns 'fs 'he 'matrix)) + (**loader-construct-from sc default names) + ) + +(define (e3d_object? l) (equal? 'e3d_object (list-ref l 0))) +(define (e3d_object-name l) (list-ref l 1)) ; Name of object (string) +(define (e3d_object-obj l) (list-ref l 2)) ; Object implementation. +(define (e3d_object-mat l) (list-ref l 3)) ; Materials for this object. +(define (e3d_object-attr l) (list-ref l 4)) ; List of attributes. +(define (make-e3d_object . sc) + (define default (list 'e3d_object 'undefined #f '() '())) + (define names (list 'name 'obj 'mat 'attr)) + (**loader-construct-from sc default names) + ) + +(define (e3d_file? l) (equal? 'e3d_file (list-ref l 0))) +(define (e3d_file-objs l) (list-ref l 1)) ; List of objects. +(define (e3d_file-mat l) (list-ref l 2)) ; List of materials. +(define (e3d_file-creator l) (list-ref l 3)) ; Creator string. +(define (e3d_file-dir l) (list-ref l 4)) ; Directory for file. +(define (make-e3d_file . sc) + (define default (list 'e3d_file '() '() "" "")) + (define names (list 'objs 'mat 'creator 'dir)) + (**loader-construct-from sc default names) + ) +; (e3d_file `(objs ,objs) `(mat ,mat)) +; + + +(define (e3d_image? l) (equal? 'e3d_image (list-ref l 0))) +(define (e3d_image-type l) (list-ref l 1)) +(define (e3d_image-bytes_pp l) (list-ref l 2)) +(define (e3d_image-alignment l) (list-ref l 3)) +(define (e3d_image-order l) (list-ref l 4)) +(define (e3d_image-width l) (list-ref l 5)) +(define (e3d_image-height l) (list-ref l 6)) +(define (e3d_image-image l) (list-ref l 7)) +(define (e3d_image-filename l) (list-ref l 8)) +(define (e3d_image-name l) (list-ref l 9)) +(define (e3d_image-extra l) (list-ref l 10)) +(define (make-e3d_image . sc) + (define default (list 'e3d_image 'r8g8b8 3 1 'lower_left 0 0 #f 'none '() '())) + (define names (list 'type 'bytes_pp 'alignment 'order 'width 'height 'image 'filename 'name 'extra)) + (**loader-construct-from sc default names) + ) + + +(define (**send-back-list l) + (newline) + (write l) + (newline) + (**flush-out)) + +(define (**send-back-list-ep l) + (newline (current-error-port)) + (write l (current-error-port)) + (newline (current-error-port)) + (**flush-out)) + +(define (**returns-reply) + (let ((reply (read))) + reply)) + +(define (**loader-get-script-directory path) + ;; Assuming only R5RS available, we need a function + ;; to get the directory. + (let ((len (string-length path))) + (let ((chr (string-ref path (- len 1))) + (sub (substring path 0 (- len 1)))) + (if (or (eq? chr #\\) (eq? chr #\/)) + (substring path 0 len) + (**loader-get-script-directory sub))))) + +;; +;; Returned replies from (**returns-reply) are always as a list, car +;; is used if there is only one value. +;; + +(define (wings-set-variable! varname varval) + (**send-back-list (list '%setvar varname varval)) + (car (**returns-reply))) +(define (wings-get-variable varname) + (**send-back-list (list '%getvar varname)) + (car (**returns-reply))) +(define (wings-query str) + (**send-back-list (list '%query str)) + (car (**returns-reply))) +(define (wings-we! . Args) + (**send-back-list (list '%we! Args)) + (**returns-reply)) +(define (wings-we . Args) + (**send-back-list (list '%we Args)) + (car (**returns-reply))) +(define (wings-previous-we! . Args) + (**send-back-list (list '%we-previous! Args)) + (**returns-reply)) +(define (wings-previous-we . Args) + (**send-back-list (list '%we-previous Args)) + (car (**returns-reply))) +;; TODO: wings-with-we-change Mod Name Args Fun +(define (wings-pb-message . Args) + (define A1 (car Args)) + (define A2 (cdr Args)) + (cond + ((eq? A2 '()) + (wings-pb-message-1 A1)) + (#t + (wings-pb-message-2 A1 (car A2))))) + +(define (wings-pb-message-2 Prc Str) + (**send-back-list-ep (list '%pbmessage Prc Str))) + +(define (wings-pb-message-1 Str) + (**send-back-list-ep (list '%pbmessage 1.0 Str))) + +(define (wings-result-text List) + (if (string? List) + (wings-result-text (list List)) + (**send-back-list (list '%resulttext List)) + ) + ) + + +;; +;; + + diff --git a/plugins_src/scripting/init_scripts/scm/init.2.scm b/plugins_src/scripting/init_scripts/scm/init.2.scm new file mode 100644 index 000000000..9f57808e7 --- /dev/null +++ b/plugins_src/scripting/init_scripts/scm/init.2.scm @@ -0,0 +1,3 @@ + +(script-loop) + diff --git a/plugins_src/scripting/init_scripts/scm/init_env_csi.scm b/plugins_src/scripting/init_scripts/scm/init_env_csi.scm new file mode 100644 index 000000000..a21fdfb4d --- /dev/null +++ b/plugins_src/scripting/init_scripts/scm/init_env_csi.scm @@ -0,0 +1,55 @@ + +;; +;; Scripting for Shapes (Scheme and Python) +;; +;; Copyright 2023 Edward Blake +;; +;; See the file "license.terms" for information on usage and redistribution +;; of this file, and for a DISCLAIMER OF ALL WARRANTIES. +;; +;; $Id$ +;; + +(define (relative_path_from_absolute Path1 Path2) + (define (split_components Path) + (define Path_1 (list->string (map (lambda (C) (if (eq? C #\\) #\/ C)) (string->list Path)))) + (filter (lambda (Str) (not (equal? "" Str))) (string-split Path_1 "/"))) + (define (detour P1 P2) + (if (not (pair? P2)) + P1 + (cons ".." (detour P1 (cdr P2)))) + ) + (define Path1L (split_components Path1)) + (define Path2L (split_components Path2)) + (if (not (equal? (car Path1L) (car Path2L))) + Path1 + (let () + (define PathList + (let loop ((Path1 Path1L) (Path2 Path2L)) + (if (not (pair? Path2)) + Path1 + (if (equal? (car Path1) (car Path2)) + (loop (cdr Path1) (cdr Path2)) + (detour Path1 Path2) + ) + ) + )) + (string-join PathList "/")) + ) + ) + +(define (filename_dir Path1) + (define (split_components Path) + (define Path_1 (list->string (map (lambda (C) (if (eq? C #\\) #\/ C)) (string->list Path)))) + (filter (lambda (Str) (not (equal? "" Str))) (string-split Path_1 "/"))) + (define Path1L (split_components Path1)) + (string-join (reverse (cdr (reverse Path1L))) "/") + ) + +;; Chicken scheme requires a flush after every list write. +(define (**flush-out) + (flush-output)) + +(define (**add-to-load-path Path) + #f) + diff --git a/plugins_src/scripting/init_scripts/scm/init_env_gauche.scm b/plugins_src/scripting/init_scripts/scm/init_env_gauche.scm new file mode 100644 index 000000000..99da10563 --- /dev/null +++ b/plugins_src/scripting/init_scripts/scm/init_env_gauche.scm @@ -0,0 +1,59 @@ + +;; +;; Scripting for Shapes (Scheme and Python) +;; +;; Copyright 2023-2025 Edward Blake +;; +;; See the file "license.terms" for information on usage and redistribution +;; of this file, and for a DISCLAIMER OF ALL WARRANTIES. +;; +;; $Id$ +;; + +(use gauche.threads) + +(define (relative_path_from_absolute Path1 Path2) + (define (split_components Path) + (define Path_1 (list->string (map (lambda (C) (if (eq? C #\\) #\/ C)) (string->list Path)))) + (filter (lambda (Str) (not (equal? "" Str))) (string-split Path_1 "/"))) + (define (detour P1 P2) + (if (not (pair? P2)) + P1 + (cons ".." (detour P1 (cdr P2)))) + ) + (define Path1L (split_components Path1)) + (define Path2L (split_components Path2)) + (if (not (equal? (car Path1L) (car Path2L))) + Path1 + (let () + (define PathList + (let loop ((Path1 Path1L) (Path2 Path2L)) + (if (not (pair? Path2)) + Path1 + (if (equal? (car Path1) (car Path2)) + (loop (cdr Path1) (cdr Path2)) + (detour Path1 Path2) + ) + ) + )) + (string-join PathList "/")) + ) + ) + +(define (filename_dir Path1) + (define (split_components Path) + (define Path_1 (list->string (map (lambda (C) (if (eq? C #\\) #\/ C)) (string->list Path)))) + (filter (lambda (Str) (not (equal? "" Str))) (string-split Path_1 "/"))) + (define Path1L (split_components Path1)) + (string-join (reverse (cdr (reverse Path1L))) "/") + ) + +;; Gauche scheme does a flush automatically after newline +(define (**flush-out) + (flush-all-ports)) + +(define (**add-to-load-path p) + ;; We only need run-time modification of the load-path variable + (set! *load-path* (cons p *load-path*)) + ) + diff --git a/plugins_src/scripting/resource/README b/plugins_src/scripting/resource/README new file mode 100644 index 000000000..07f8cddca --- /dev/null +++ b/plugins_src/scripting/resource/README @@ -0,0 +1,11 @@ +After updating the xbm bitmap, some programs include the entire absolute path +inside of the xbm file, which breaks the xbm file. The beginning of the xbm +file should look like this: + +#define wpc_shape_from_scripts_btn_refresh_width 32 +#define wpc_shape_from_scripts_btn_refresh_height 32 +static unsigned char wpc_shape_from_scripts_btn_refresh_bits[] = { + +Use xbm_to_hrl.escript to update the hrl from the xbm. + + diff --git a/plugins_src/scripting/resource/wpc_scripting_shapes_btn_refresh.hrl b/plugins_src/scripting/resource/wpc_scripting_shapes_btn_refresh.hrl new file mode 100644 index 000000000..ae0269e37 --- /dev/null +++ b/plugins_src/scripting/resource/wpc_scripting_shapes_btn_refresh.hrl @@ -0,0 +1,4 @@ +%% This file is generated from the .xbm file. +-define(WPC_SHAPE_FROM_SCRIPTS_BTN_REFRESH_WIDTH, 32). +-define(WPC_SHAPE_FROM_SCRIPTS_BTN_REFRESH_HEIGHT, 32). +-define(WPC_SHAPE_FROM_SCRIPTS_BTN_REFRESH_BITS, <<16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#40,16#00,16#00,16#00,16#C0,16#00,16#00,16#00,16#C0,16#01,16#00,16#00,16#FF,16#F3,16#00,16#80,16#FF,16#F7,16#01,16#C0,16#FF,16#F3,16#03,16#C0,16#C3,16#C1,16#03,16#C0,16#C1,16#80,16#03,16#C0,16#41,16#80,16#03,16#C0,16#01,16#80,16#03,16#C0,16#01,16#80,16#03,16#C0,16#01,16#80,16#03,16#C0,16#01,16#80,16#03,16#C0,16#01,16#80,16#03,16#C0,16#01,16#80,16#03,16#C0,16#01,16#80,16#03,16#C0,16#01,16#80,16#03,16#C0,16#01,16#81,16#03,16#C0,16#81,16#81,16#03,16#C0,16#C3,16#C1,16#03,16#C0,16#E7,16#FF,16#03,16#80,16#F7,16#FF,16#01,16#00,16#E7,16#FF,16#00,16#00,16#C0,16#01,16#00,16#00,16#80,16#01,16#00,16#00,16#00,16#01,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00>>). diff --git a/plugins_src/scripting/resource/wpc_scripting_shapes_btn_refresh.xbm b/plugins_src/scripting/resource/wpc_scripting_shapes_btn_refresh.xbm new file mode 100644 index 000000000..c4c7cd383 --- /dev/null +++ b/plugins_src/scripting/resource/wpc_scripting_shapes_btn_refresh.xbm @@ -0,0 +1,14 @@ +#define wpc_shape_from_scripts_btn_refresh_width 32 +#define wpc_shape_from_scripts_btn_refresh_height 32 +static unsigned char wpc_shape_from_scripts_btn_refresh_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x40, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xC0, 0x01, 0x00, + 0x00, 0xFF, 0xF3, 0x00, 0x80, 0xFF, 0xF7, 0x01, 0xC0, 0xFF, 0xF3, 0x03, + 0xC0, 0xC3, 0xC1, 0x03, 0xC0, 0xC1, 0x80, 0x03, 0xC0, 0x41, 0x80, 0x03, + 0xC0, 0x01, 0x80, 0x03, 0xC0, 0x01, 0x80, 0x03, 0xC0, 0x01, 0x80, 0x03, + 0xC0, 0x01, 0x80, 0x03, 0xC0, 0x01, 0x80, 0x03, 0xC0, 0x01, 0x80, 0x03, + 0xC0, 0x01, 0x80, 0x03, 0xC0, 0x01, 0x80, 0x03, 0xC0, 0x01, 0x81, 0x03, + 0xC0, 0x81, 0x81, 0x03, 0xC0, 0xC3, 0xC1, 0x03, 0xC0, 0xE7, 0xFF, 0x03, + 0x80, 0xF7, 0xFF, 0x01, 0x00, 0xE7, 0xFF, 0x00, 0x00, 0xC0, 0x01, 0x00, + 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; diff --git a/plugins_src/scripting/scripting_engines.erl b/plugins_src/scripting/scripting_engines.erl new file mode 100644 index 000000000..44984104e --- /dev/null +++ b/plugins_src/scripting/scripting_engines.erl @@ -0,0 +1,238 @@ +%% +%% Scripting for Shapes Engines +%% +%% Read script engine config files and set up the arguments for when +%% a script interpreter is invoked. +%% +%% Copyright 2024-2025 Edward Blake +%% +%% See the file "license.terms" for information on usage and redistribution +%% of this file, and for a DISCLAIMER OF ALL WARRANTIES. +%% +%% $Id$ +%% + +-module(scripting_engines). + +-export([init/1, all_engines/0, script_interp/3, extra_script_dirs/0]). + +-include_lib("kernel/include/file.hrl"). + +init(DirName) -> + InitDir = filename:absname(code:where_is_file(DirName)), + catch ets:new(script_eng, [public,ordered_set,named_table]), + find_engines(InitDir), + find_script_dirs(InitDir). + +all_engines() -> + [{all, AllEng}] = ets:lookup(script_eng, all), + AllEng. + +script_interp(Type, Settings, InitScriptsDir) -> + case init_file(Type) of + false -> + Interpreter = none, + Arguments = [], + InitFile = ""; + InitFile0 when is_list(InitFile0) -> + Atom1 = list_to_atom("setting_" ++ Type ++ "_int_path"), + Atom2 = list_to_atom("setting_" ++ Type ++ "_arguments"), + Interpreter = auto_fill_int_path(Type, + proplists:get_value(Atom1, Settings, "")), + Arguments = auto_fill_arguments(Type, Interpreter, + string:split(proplists:get_value(Atom2, Settings, ""), " ", all), + InitScriptsDir), + InitFile = filename:join(InitScriptsDir, InitFile0) + end, + {Interpreter, Arguments, InitFile}. + +auto_fill_int_path(Type, Str) -> + Atom = list_to_atom("setting_autointrp_" ++ Type), + auto_fill_int_path(Type, Str, Atom, int_list(Type)). +auto_fill_int_path(_, Str, Atom, List) -> + case Str of + "" -> + find_first_interpreter(Atom, List); + _ -> + Str + end. + +auto_fill_arguments(Type, Interpreter, Extra, BaseDir) -> + Interpreter0 = filename:basename(filename:rootname(Interpreter)), + Extra_1 = auto_fill_arguments_no_empty(Extra), + case get_auto_fill(Type, Interpreter0) of + false -> Extra_1; + Args -> + [ expand_arg(A, BaseDir) || A <- Args] + ++ Extra_1 + end. + +expand_arg("%BASEDIR%" ++ Path, BaseDir) -> + case Path of + [] -> + BaseDir; + [C|Path1] when C =:= $/; C =:= $\\ -> + filename:join(BaseDir,Path1) + end; +expand_arg(A, _) when is_list(A) -> A; +expand_arg({wrap, A, B}, BaseDir) when is_list(A) -> + lists:flatten(string:replace(A, "%s", expand_arg(B, BaseDir), all)); +expand_arg({concat, A, B}, BaseDir) -> + expand_arg(A, BaseDir) ++ expand_arg(B, BaseDir); +expand_arg({esc, A}, BaseDir) -> + esc(expand_arg(A, BaseDir)); +expand_arg({absname, A, B}, BaseDir) -> + filename:absname(expand_arg(A, BaseDir), expand_arg(B, BaseDir)). + +esc(File) -> + File_1 = lists:flatten(string:replace(File, "\\", "\\\\", all)), + lists:flatten(string:replace(File_1, "\"", "\\\"", all)). + +auto_fill_arguments_no_empty(Extra) -> + case Extra of + [[]] -> + []; + _ -> + Extra + end. + +find_first_interpreter(_AutoSetting, List) -> + Found = find_first_interpreter_1(List), + Found. +find_first_interpreter_1([TryCmd | List]) -> + case os:find_executable(TryCmd) of + Found when is_list(Found) -> TryCmd; + false -> + find_first_interpreter_1(List) + end; +find_first_interpreter_1([]) -> + "". + +%% +%% + +find_engines(Path) -> + case file:list_dir(Path) of + {ok, Filenames} -> + Found = + [ read_eng_conf(Path,AName) + || AName <- Filenames], + Found_1 = [A || A <- Found, A =/= false ], + ets:insert(script_eng, {all, Found_1}), + ok; + _ -> + false + end. + +read_eng_conf(Path,Name) -> + case filename:extension(Name) of + ".script-init-conf" -> + case file:consult(filename:join(Path,Name)) of + {ok, [Tuple|_]} -> + {A,B,C,D,E} = Tuple, + engine_conf(A,B,C,D,E), + {A,B} + end; + _ -> false + end. + +engine_conf(Type, _HasArgs, InitFile, Commands, Extra) -> + ets:insert(script_eng, {{init_file,Type},InitFile}), + ets:insert(script_eng, {{int_list,Type}, Commands}), + + [ engine_conf_auto_fill(Type,Cmd,Args) + || {Cmd,Args} <- proplists:get_value(auto_fill, Extra, []) ]. + +engine_conf_auto_fill(Type,Cmd,Args) -> + ets:insert(script_eng, {{auto_fill,{Type,Cmd}}, Args}). + +init_file(Type) -> + case ets:lookup(script_eng, {init_file, Type}) of + [{_,InitFile}] -> InitFile; + _ -> false + end. + +int_list(Type) -> + case ets:lookup(script_eng, {int_list, Type}) of + [{_,IntList}] -> IntList; + _ -> false + end. + +get_auto_fill(Type,Cmd) -> + case ets:lookup(script_eng, {auto_fill, {Type,Cmd}}) of + [{_,Args}] -> Args; + _ -> false + end. + +%% +%% + +find_script_dirs(InitDir) -> + [_|Dir0] = lists:reverse(filename:split(InitDir)), + Dir = filename:join(lists:reverse(Dir0)), + + ScriptDir = filename:join(Dir, "scripts"), + case file:read_file_info(ScriptDir) of + {ok,#file_info{type=directory}=_} -> + ets:insert(script_eng, {scriptdir1, [ScriptDir]}); + _ -> + ok + end, + ScriptPaths = filename:join(InitDir, "paths"), + case file:list_dir(ScriptPaths) of + {ok,List} -> + Paths_0 = + [read_path_file(filename:join(ScriptPaths, File)) + || File <- List], + Paths = lists:append(Paths_0), + ets:insert(script_eng, {scriptdirs, Paths}); + _ -> + ok + end. + +read_path_file(File) -> + case filename:extension(File) of + ".list" -> + case file:read_file(File) of + {ok, List0} -> + List = [read_path_file_1(A) + || A <- string:split(binary_to_list(List0), "\n", all)], + [A || A <- List, A =/= false, A =/= ""]; + _ -> [] + end; + _ -> [] + end. + +read_path_file_1("#" ++ _) -> false; +read_path_file_1(File) -> + Path = string:trim(File), + case Path of + "" -> false; + "/" ++ _ -> + read_path_file_2(Path); + "~/" ++ Path_1 -> + read_path_file_2(filename:join(os:getenv("HOME"), Path_1)); + _ -> false + end. + +read_path_file_2(File) -> + case file:read_file_info(File) of + {ok,#file_info{type=directory}=_} -> + File; + _ -> + false + end. + +%% +%% + +%% Get extra script directories apart from user supplied ones. +%% +extra_script_dirs() -> + lists:append([ + case ets:lookup(script_eng, Atom) of + [{_,List}] -> List; + _ -> [] + end + || Atom <- [scriptdir1, scriptdirs]]). + diff --git a/plugins_src/scripting/tools/gen-funs-doc.awk b/plugins_src/scripting/tools/gen-funs-doc.awk new file mode 100644 index 000000000..85df79794 --- /dev/null +++ b/plugins_src/scripting/tools/gen-funs-doc.awk @@ -0,0 +1,211 @@ + +## +## This generates the function documentation (mainly the we +## functions) for the Python3 and Scheme scripting manuals. +## +## No extensions are used so this should work with most versions +## of awk. +## +## Copyright 2025 Edward Blake +## +## See the file "license.terms" for information on usage and redistribution +## of this file, and for a DISCLAIMER OF ALL WARRANTIES. +## +## $Id$ +## + + + +function ts (s) { + gsub("^[ ]+|[ ]+$","",s) + return s +} + +function snk (s) { + ## Make snake case + v = "" + l = split(s, chrs, ""); + for (i2 = 1; i2 <= l; i2++) { + c2 = chrs[i2]; + if (match(c2, "[A-Z]")) { + if (i2 > 1) { + v = v "_"; + } + v = v tolower(c2); + } else { + v = v c2; + } + } + return v +} + +## Print an argument formatted for either Scheme or Python +## +function print_args (s) { + c = split(s, args, " +"); + if (type == "scm") { + for (i = 1; i <= c; i ++) { + if (i > 1) { + printf " "; + } + printf "%s", args[i]; + } + } + if (type == "py") { + for (i = 1; i <= c; i ++) { + if (i > 1) { + printf ", "; + } + printf "%s", snk(args[i]); + } + } +} + +## Substitute one placeholder +## +function note_arg (s) { + if (type == "scm") { + if (match(s, "^TUPLE ")) { + cont = substr(s, RLENGTH+1); + return "#(" cont ")"; + } + if (match(s, "^ARG ")) { + cont = substr(s, RLENGTH+1); + return cont; + } + if (match(s, "^ATOM ")) { + cont = substr(s, RLENGTH+1); + return "'" cont; + } + } + if (type == "py") { + if (match(s, "^TUPLE ")) { + cont = substr(s, RLENGTH+1); + cont2 = ""; + c = split(cont, conta, " +"); + for (i = 1; i <= c; i++) { + if (i > 1) { + cont2 = cont2 ", "; + } + cont2 = cont2 snk(conta[i]) + } + return "(" cont2 ")"; + } + if (match(s, "^ARG ")) { + cont = substr(s, RLENGTH+1); + return snk(cont); + } + if (match(s, "^ATOM ")) { + cont = substr(s, RLENGTH+1); + return "'" cont "'"; + } + } + + return "??"; +} + +## Parse out the text placeholders and substitute them +## with formatted text (for tuples, atoms, etc). +function notes2 (s) { + str2 = "" + while (match(s, "\\$\\(")) { + str2 = str2 substr(s, 0, RSTART-1); + s = substr(s, RSTART+RLENGTH); + until = index(s, ")"); + str2 = str2 note_arg(substr(s, 0, until-1)); + s = substr(s, until+1); + } + str2 = str2 s; + return str2; +} + +function notes (s) { + return notes2(s); +} + +BEGIN { + FS = "\\|" + funname0 = "" +} + +## Some notes that should be added to the generated text. +NF == 2 { + if ($1 == "") { + note = ts($2); + print notes(note); + } +} + +## A function definition +NF > 5 { + section = ts($1); + module = ts($2); + funname = ts($3); + changeswe = ts($4); + reqargs = ts($5) + optargs = ts($6) + returns = ts($7) + + ## When it is a different function name, add a line break + if (funname0 != funname) { + printf "\n"; + funname0 = funname; + } + + ## The documentation for Scheme scripting + if (type == "scm") { + printf "("; + if (section != "") { + printf "%s:", section; + } + printf "%s:%s%s", module, funname, changeswe; + if ((reqargs != "") || (optargs != "")) { + printf "\n "; + if (reqargs != "") { + print_args(reqargs); + if (optargs != "") { + printf "\n "; + } + } + if (optargs != "") { + printf "optional "; + print_args(optargs); + } + } + printf ")\n"; + } + + ## The documentation for Python3 scripting + if (type == "py") { + printf "%s__%s(", module, funname; + if (reqargs != "") { + print_args(reqargs); + } + if (optargs != "") { + if (reqargs != "") { + printf " "; + } + printf "["; + if (reqargs != "") { + printf ", "; + } + print_args(optargs); + printf "]"; + } + printf ")\n"; + if (changeswe == "!") { + printf "! Changes #we{}\n"; + } + } + + if (returns != "") { + if (returns != "_") { + printf "returns: %s\n", notes(returns); + } + } +} +END { +} + + + diff --git a/plugins_src/scripting/tools/gen-init-we-script.sh b/plugins_src/scripting/tools/gen-init-we-script.sh new file mode 100755 index 000000000..6f19b71e1 --- /dev/null +++ b/plugins_src/scripting/tools/gen-init-we-script.sh @@ -0,0 +1,51 @@ +#!/bin/sh + +## +## Generates the init.scm for scheme, or the w3d_we.py file for python. +## +## gen-init-we-script.sh +## +## Copyright 2025 Edward Blake +## +## See the file "license.terms" for information on usage and redistribution +## of this file, and for a DISCLAIMER OF ALL WARRANTIES. +## +## $Id$ +## + +dir="$(dirname $0)" + +file1=$1; shift +file2=$1; shift +callable=$1; shift +modnames=$1; shift +modtype=$1; shift + +if [ ! -f "${file1}" ] || [ ! -f "${callable}" ] || [ ! -f "${modnames}" ] +then + echo "file does not exist" 1>&2 + exit 1 +fi + +if [ "${modtype}" != "py" ] && [ "${modtype}" != "scm" ] +then + echo "not py or scm" 1>&2 + exit 1 +fi + +cat "${file1}" + +escript ${dir}/gen-wrapper-script.escript \ + "${callable}" "${modnames}" "${modtype}" + +if [ "${file2}" != "-" ] +then + if [ ! -f "${file2}" ] + then + echo "${file2} not a file" 1>&2 + exit 1 + fi + + cat "${file2}" +fi + diff --git a/plugins_src/scripting/tools/gen-wrapper-script.escript b/plugins_src/scripting/tools/gen-wrapper-script.escript new file mode 100644 index 000000000..b4c8c469c --- /dev/null +++ b/plugins_src/scripting/tools/gen-wrapper-script.escript @@ -0,0 +1,283 @@ +#!/usr/bin/env escript + +%% +%% Generates we interface functions for python and scheme. +%% +%% Copyright 2025 Edward Blake +%% +%% See the file "license.terms" for information on usage and redistribution +%% of this file, and for a DISCLAIMER OF ALL WARRANTIES. +%% +%% $Id$ +%% + +%% Usage: +%% escript gen-wrapper-script.escript +%% +%% = path to callable.conf +%% = path to modnames file +%% = py for python source, scm for scheme source +%% +%% Script is generated to standard output + +changes_we(_Arity,Args,Returns) -> + lists:member(we, Args) andalso + changes_we_1(Returns). + +changes_we_1(we) -> + true; +changes_we_1(List) when is_list(List) -> + lists:member(we, List); +changes_we_1(_) -> + false. + +wings_we(py, false) -> + "wings_we__get"; +wings_we(py, true) -> + "wings_we__change"; +wings_we(scm, false) -> + "wings-we"; +wings_we(scm, true) -> + "wings-we!". + +arg_list(List,Cap) -> + arg_list(List,Cap,1). +arg_list([],_,_) -> + []; +arg_list([Atom|List],Cap,I) when Atom =:= we; Atom =:= st -> + arg_list(List,Cap,I); +arg_list([_|List],Cap,I) -> + [ arg_list_1(I,Cap) | + arg_list(List,Cap,I+1) ]. +arg_list_1(I,false) -> + "arg" ++ [$0 + I]; +arg_list_1(I,true) -> + "Arg" ++ [$0 + I]. + + +arg_types(List,Cap) -> + arg_types(List,Cap,1). +arg_types([],_,_) -> + []; +arg_types([Atom|List],Cap,I) when Atom =:= we; Atom =:= st -> + arg_types(List,Cap,I); +arg_types([Type|List],Cap,I) -> + [ arg_types_1(Type) | + arg_types(List,Cap,I+1) ]. +arg_types_1(Type) -> + atom_to_list(Type). + +arg_types_ret(we) -> []; +arg_types_ret(Arg) when is_atom(Arg) -> + arg_types_1(Arg); +arg_types_ret(Return0) when is_list(Return0) -> + Return = lists:filter( + fun (Atom) when Atom =:= we; Atom =:= st -> false; (_) -> true end, + Return0), + arg_types(Return,true). + +str_excl(true) -> + "!"; +str_excl(_) -> + "". + +str_excl_py(CommentStr, true) -> + [CommentStr, "Changes #we{}"]; +str_excl_py(_, _) -> + "". + + +arg_if_py([]) -> + ""; +arg_if_py(ArgList0) -> + lists:flatten(lists:join(", ",ArgList0)) ++ ", ". + +arg_if_py2([]) -> + ""; +arg_if_py2(ArgList0) -> + ", " ++ lists:flatten(lists:join(", ",ArgList0)). + +arg_if_scm([]) -> + ""; +arg_if_scm(ArgList0) -> + " " ++ lists:flatten(lists:join(" ", ArgList0)). + +arg_list_smallest([{_,{Args,_}}|List]) -> + arg_list_smallest(List, Args). +arg_list_smallest([], Args0) -> + Args0; +arg_list_smallest([{_,{Args,_}}|List], Args0) -> + case length(Args0) >= length(Args) of + true -> + arg_list_smallest(List, Args); + _ -> + arg_list_smallest(List, Args0) + end. + +arg_types_string(CommentStr,_ArgsL0,Return) -> + Return1 = arg_types_ret(Return), + Str0 = + [%[[A, " "] || A <- arg_types(ArgsL0,true)], + case Return1 of [] -> []; _ -> ["",Return1] end], % "-> " + Str = string:trim(lists:flatten(Str0)), + case Str of + "" -> + ""; + _ -> + [CommentStr,string:trim(lists:flatten(Str))] + end. + +print_fun(_,{_,[]},_) -> + ok; +print_fun(py,{_,[{{Mod,Name,Arity}, {Args,Return}}]},MList) -> + {ok, MNames0} = maps:find(Mod,MList), + MNames = [atom_to_list(A) || A <- MNames0 ++ [Name]], + ChangesWe = changes_we(Arity,Args,Return), + ArgList = lists:join(", ",arg_list(Args,false)), + Types = arg_types_string(" ## ",Args,Return), + ArgList1 = case ArgList of + [] -> ""; + _ -> + [", ", ArgList] + end, + io:format("def ~s(~s):~s~n", [lists:join("__",MNames),ArgList,str_excl_py(" ## ",ChangesWe)]), + io:format(" return ~s('~w', '~w'~s)~s~n", [wings_we(py,ChangesWe),Mod,Name,ArgList1,Types]), + ok; +print_fun(py,{_,List0},MList) -> + List = lists:reverse(List0), + [{{Mod0,Name0,Arity0},{Args0,Return0}}|_] = List, + {ok, MNames0} = maps:find(Mod0,MList), + MNames = [atom_to_list(A) || A <- MNames0 ++ [Name0]], + ChangesWe = changes_we(Arity0,Args0,Return0), + ArgList0 = arg_list(arg_list_smallest(List),false), + io:format("def ~s(~s*args):~s~n", + [ lists:join("__",MNames), + arg_if_py(ArgList0),str_excl_py(" ## ",ChangesWe)]), + io:format(" argsize = len(args)~n", []), + + each_arg(fun + (First, Extra, {{Mod,Name,_Arity}, {ArgsL0,Return}}) -> + Types = arg_types_string(" ## ",ArgsL0,Return), + case First of + true -> + io:format(" if argsize == ~w:~n", [length(Extra)]); + _ -> + io:format(" elif argsize == ~w:~n", [length(Extra)]) + end, + lists:foldl(fun (Arg, I) -> + io:format(" ~s = args[~w]~n", [Arg, I]), + I+1 + end, 0, Extra), + io:format(" return ~s('~w', '~w'~s~s)~s~n", + [ wings_we(py,ChangesWe), + Mod, + Name, + arg_if_py2(ArgList0), + arg_if_py2(Extra), + Types]) + end, ArgList0, false, List), + + io:format(" else:~n", []), + io:format(" raise 'invalid arguments'~n", []), + ok; + +print_fun(scm,{_,[{{Mod,Name,Arity},{Args,Return}}]},MList) -> + {ok, MNames0} = maps:find(Mod,MList), + ChangesWe = changes_we(Arity,Args,Return), + MNames = [atom_to_list(A) || A <- MNames0 ++ [Name]], + ArgList = arg_list(Args,true), + Types = arg_types_string(" ;; ",Args,Return), + io:format("(define (~s~s~s) (~s '~w '~w~s))~s~n", + [ lists:join(":",MNames), + str_excl(ChangesWe), + case ArgList of [] -> ""; _ -> " " ++ lists:join(" ",ArgList) end, + wings_we(scm,ChangesWe), + Mod, + Name, + arg_if_scm(ArgList), + Types]), + ok; +print_fun(scm,{_,List0},MList) -> + List = lists:reverse(List0), + [{{Mod0,Name0,Arity0},{Args0,Return0}}|_] = List, + {ok, MNames0} = maps:find(Mod0,MList), + ChangesWe = changes_we(Arity0,Args0,Return0), + MNames = [atom_to_list(A) || A <- MNames0 ++ [Name0]], + ArgList0 = arg_list(arg_list_smallest(List),true), + + io:format("(define (~s~s~s . Args)~n", + [ lists:join(":",MNames), + str_excl(ChangesWe), + arg_if_scm(ArgList0)]), + io:format(" (case (length Args)~n", []), + + each_arg(fun + (_First, [], {{Mod,Name,_Arity}, {ArgsL0,Return}}) -> + Types = arg_types_string(" ;; ",ArgsL0,Return), + io:format(" ((0) (~s '~w '~w~s))~s~n", + [ wings_we(scm,ChangesWe), + Mod, + Name, + arg_if_scm(ArgList0), + Types]); + + (_First, Extra, {{Mod,Name,_Arity}, {ArgsL0,Return}}) -> + Types = arg_types_string(" ;; ",ArgsL0,Return), + io:format(" ((~w) (let-values (((~s) (apply values Args) ))~n", + [ length(Extra), lists:join(" ",Extra) ]), + io:format(" (~s '~w '~w~s~s)))~s~n", + [ wings_we(scm,ChangesWe), + Mod, + Name, + arg_if_scm(ArgList0), + arg_if_scm(Extra), + Types]) + + end, ArgList0, true, List), + + io:format(" ))~n", []), + ok. + +each_arg(Fun, Args0, UVar, List) -> + each_arg(Fun, Args0, UVar, List, true). + +each_arg(_, _, _, [], _) -> + ok; +each_arg(Fun, Args0, UVar, [{{_,_,_Arity},{Args,_}}=Attr|List], First) -> + ArgList = lists:nthtail(length(Args0),arg_list(Args,UVar)), + Fun(First, ArgList, Attr), + each_arg(Fun, Args0, UVar, List, false). + + +group_by_module_1({{Mod,Name,_},_}=Attr, {{Mod1,Name1},Acc}, _Fun) + when Name =:= Name1, Mod =:= Mod1 -> + {{Mod,Name},[Attr|Acc]}; +group_by_module_1({{Mod,Name,_},_}=Attr, {{Mod1,Name1},_}=Acc, Fun) when Name =/= Name1 -> + Fun(Acc), + case Mod1 =:= Mod of + true -> + ok; + false -> + io:format("~n",[]) + end, + {{Mod,Name},[Attr]}. + +group_by_module(FunList,ModuleNames,Type) -> + ModuleNames_1 = maps:from_list(ModuleNames), + Fun = case Type of + "py" -> + fun (A) -> print_fun(py,A,ModuleNames_1) end; + "scm" -> + fun (A) -> print_fun(scm,A,ModuleNames_1) end + end, + Fun(lists:foldl( + fun (A,B) -> + group_by_module_1(A,B,Fun) + end, {{none,none},[]}, FunList)). + +main([File,ModNames,Type]) -> + {ok, [FunList]} = file:consult(File), + {ok, [ModuleNames]} = file:consult(ModNames), + group_by_module(FunList,ModuleNames,Type). + + diff --git a/plugins_src/scripting/tools/xbm_to_hrl.escript b/plugins_src/scripting/tools/xbm_to_hrl.escript new file mode 100755 index 000000000..d154f8949 --- /dev/null +++ b/plugins_src/scripting/tools/xbm_to_hrl.escript @@ -0,0 +1,72 @@ +#!/bin/env escript +-module(xbm_to_hrl). + +%% +%% Turn xbm files into hrl files +%% +%% Copyright 2025 Edward Blake +%% +%% See the file "license.terms" for information on usage and redistribution +%% of this file, and for a DISCLAIMER OF ALL WARRANTIES. +%% +%% $Id$ +%% + +main([]) -> + io:format("xbm_to_hrl \n\n",[]); +main([Xbmfile]) -> + NewFile_0 = filename:rootname(Xbmfile), + case file:read_file_info(Xbmfile) of + {ok, _} -> + NewFile = NewFile_0 ++ ".hrl", + {ok, XBMF} = file:open(Xbmfile, [read]), + {ok, HRLF} = file:open(NewFile, [write]), + io:format(HRLF, "%% This file is generated from the .xbm file.\n", []), + to_hrl(XBMF, HRLF), + file:close(XBMF), + file:close(HRLF); + _ -> + io:format("Could not find $xbmfile\n\n",[]) + end. + +to_hrl(XBMF, HRLF) -> + Bits = [], + Bitsname = "??", + to_hrl(XBMF, HRLF, Bits, Bitsname). +to_hrl(XBMF, HRLF, Bits, Bitsname) -> + case io:get_line(XBMF, "") of + [_|_]=Line_0 -> + Line = string:trim(Line_0), + case re:run(Line, "^#define +(\\w+)(_width|_height) +([0-9]+)",[{capture,all,list}]) of + {match,[_,S1,S2,S3]} -> + Name = string:to_upper(S1 ++ S2), + io:format(HRLF, "-define(~s, ~s).\n", [Name, S3]), + Bitsname_1 = Bitsname, + Bits_1 = Bits; + _ -> + case re:run(Line, "static +unsigned +char +(\\w+)(_bits) *\\[ *\\] *= *{",[{capture,all,list}]) of + {match,[_,S1,S2]} -> + Bitsname_1 = string:to_upper(S1 ++ S2); + _ -> + Bitsname_1 = Bitsname + end, + case re:run(Line, "^ *0x") of + {match,_} -> + Vals = string:split(Line, ",", all), + Bits_1 = lists:foldl(fun(Val, Acc) -> + case re:run(Val, "0x([0-9A-Za-z]+)",[{capture,all,list}]) of + {match,[_,M1]} -> + ["16#" ++ M1|Acc]; + _ -> + Acc + end + end, Bits, Vals); + _ -> + Bits_1 = Bits + end + end, + to_hrl(XBMF, HRLF, Bits_1, Bitsname_1); + eof -> + io:format(HRLF, "-define(~s, <<~s>>).\n", [Bitsname, lists:join(",", lists:reverse(Bits))]) + end. + diff --git a/plugins_src/scripting/tools/xbm_to_hrl_test/test.hrl b/plugins_src/scripting/tools/xbm_to_hrl_test/test.hrl new file mode 100644 index 000000000..ae0269e37 --- /dev/null +++ b/plugins_src/scripting/tools/xbm_to_hrl_test/test.hrl @@ -0,0 +1,4 @@ +%% This file is generated from the .xbm file. +-define(WPC_SHAPE_FROM_SCRIPTS_BTN_REFRESH_WIDTH, 32). +-define(WPC_SHAPE_FROM_SCRIPTS_BTN_REFRESH_HEIGHT, 32). +-define(WPC_SHAPE_FROM_SCRIPTS_BTN_REFRESH_BITS, <<16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#40,16#00,16#00,16#00,16#C0,16#00,16#00,16#00,16#C0,16#01,16#00,16#00,16#FF,16#F3,16#00,16#80,16#FF,16#F7,16#01,16#C0,16#FF,16#F3,16#03,16#C0,16#C3,16#C1,16#03,16#C0,16#C1,16#80,16#03,16#C0,16#41,16#80,16#03,16#C0,16#01,16#80,16#03,16#C0,16#01,16#80,16#03,16#C0,16#01,16#80,16#03,16#C0,16#01,16#80,16#03,16#C0,16#01,16#80,16#03,16#C0,16#01,16#80,16#03,16#C0,16#01,16#80,16#03,16#C0,16#01,16#80,16#03,16#C0,16#01,16#81,16#03,16#C0,16#81,16#81,16#03,16#C0,16#C3,16#C1,16#03,16#C0,16#E7,16#FF,16#03,16#80,16#F7,16#FF,16#01,16#00,16#E7,16#FF,16#00,16#00,16#C0,16#01,16#00,16#00,16#80,16#01,16#00,16#00,16#00,16#01,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00>>). diff --git a/plugins_src/scripting/tools/xbm_to_hrl_test/test.xbm b/plugins_src/scripting/tools/xbm_to_hrl_test/test.xbm new file mode 100644 index 000000000..c4c7cd383 --- /dev/null +++ b/plugins_src/scripting/tools/xbm_to_hrl_test/test.xbm @@ -0,0 +1,14 @@ +#define wpc_shape_from_scripts_btn_refresh_width 32 +#define wpc_shape_from_scripts_btn_refresh_height 32 +static unsigned char wpc_shape_from_scripts_btn_refresh_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x40, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xC0, 0x01, 0x00, + 0x00, 0xFF, 0xF3, 0x00, 0x80, 0xFF, 0xF7, 0x01, 0xC0, 0xFF, 0xF3, 0x03, + 0xC0, 0xC3, 0xC1, 0x03, 0xC0, 0xC1, 0x80, 0x03, 0xC0, 0x41, 0x80, 0x03, + 0xC0, 0x01, 0x80, 0x03, 0xC0, 0x01, 0x80, 0x03, 0xC0, 0x01, 0x80, 0x03, + 0xC0, 0x01, 0x80, 0x03, 0xC0, 0x01, 0x80, 0x03, 0xC0, 0x01, 0x80, 0x03, + 0xC0, 0x01, 0x80, 0x03, 0xC0, 0x01, 0x80, 0x03, 0xC0, 0x01, 0x81, 0x03, + 0xC0, 0x81, 0x81, 0x03, 0xC0, 0xC3, 0xC1, 0x03, 0xC0, 0xE7, 0xFF, 0x03, + 0x80, 0xF7, 0xFF, 0x01, 0x00, 0xE7, 0xFF, 0x00, 0x00, 0xC0, 0x01, 0x00, + 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; diff --git a/plugins_src/scripting/wpc_scripting_shapes.erl b/plugins_src/scripting/wpc_scripting_shapes.erl new file mode 100644 index 000000000..254fb5dc3 --- /dev/null +++ b/plugins_src/scripting/wpc_scripting_shapes.erl @@ -0,0 +1,4995 @@ +%% +%% Scripting (Scheme and Python) +%% +%% Use New shape, importers, exporters and command scripts in Wings3D +%% using scripting languages like Scheme and Python. +%% +%% Requires Gauche for Scheme runtime +%% Requires Python 3.6 or later +%% +%% Copyright 2023-2025 Edward Blake +%% +%% Thanks micheus for help with Script Preference Dialog +%% +%% See the file "license.terms" for information on usage and redistribution +%% of this file, and for a DISCLAIMER OF ALL WARRANTIES. +%% +%% $Id$ +%% + +-module(wpc_scripting_shapes). +-export([init/0,menu/2,command/2,installing_archive/2]). + +-include_lib("wings/src/wings.hrl"). +-include_lib("wings/e3d/e3d.hrl"). +-include_lib("wings/e3d/e3d_image.hrl"). + +-record(command_rec, { + wscrcont, + scrfile :: file:filename_all() | none, + scrtype :: string() | none, + extrafileinputs=[] +}). + +-include("resource/wpc_scripting_shapes_btn_refresh.hrl"). + +-type wings_op_mode() :: atom(). + +-ifdef(DEBUG_1). +-define(DEBUG_FMT(A,B), io:format(A,B)). +-else. +-define(DEBUG_FMT(A,B), none). +-endif. + +%% For the queries mini language +-record(crun_state, { + p = none, %% Current value when in etp_run + sett_vars = #{}, %% Variables such as for script_params, import_params, export_params + temp_vars = #{} %% Temporary variable storage, 'we' is stored here for command scripts +}). + +-define(PATH_INIT, "_init"). +init_dir() -> + atom_to_list(?MODULE) ++ ?PATH_INIT. + +init() -> + scripting_engines:init(init_dir()), + set_pref_defaults(), + catch ets:new(script_eng, [public,ordered_set,named_table]), + + %% Directories in the user_data folder for plugin_scripts and script_folders. + UserDataDir = filename:join(wings_u:basedir(user_data), wpa:version()), + PluginScriptsDir = filename:join(UserDataDir, "plugin_scripts"), + ScriptFoldersDir = filename:join(UserDataDir, "script_folders"), + + %% Note: to add another directory to find plugin_scripts and script_folders + %% (for example in the program's folder), add the extra directory to + %% the list before PluginScriptsDir and ScriptFoldersDir. + {Plugins,PluginsMenu}=get_pluginscripts([PluginScriptsDir]), + {List,ListMenu}=get_scriptfolders([ScriptFoldersDir]), + List1 = get_pluginscripts_1(PluginsMenu++ListMenu, Plugins++List), + ets:insert(script_eng, {plugin_scripts,List1}), + true. + +menu_enabled(Where,Menu,Fun) -> + case wpa:pref_get(?MODULE, setting_enable, true) of + true -> Fun(menu_1(Where,Menu)); + _ -> Menu + end. +menu_enabled(Atom,Where,Menu,Fun) + when is_atom(Atom) -> + case wpa:pref_get(?MODULE, setting_enable, true) of + true -> + Menu1 = menu_1(Where,Menu), + case wpa:pref_get(?MODULE, Atom, true) of + true -> Fun(Menu1); + _ -> Menu1 + end; + _ -> + Menu + end. + +menu({shape}=Where, Menu) -> + menu_enabled(Where, Menu, + fun(Menu1) -> Menu1 ++ [separator|from_script_menu(shape)] end); +menu({file,import}=Where, Menu) -> + menu_enabled(setting_enable_import, Where, Menu, + fun(Menu1) -> Menu1 ++ from_script_menu(import) end); +menu({file,Op}=Where, Menu) + when Op =:= export; + Op =:= export_selected -> + menu_enabled(setting_enable_export, Where, Menu, + fun(Menu1) -> Menu1 ++ from_script_menu(export) end); +menu({Mode}=Where,Menu) + when Mode =:= vertex; + Mode =:= edge; + Mode =:= face; + Mode =:= body -> + menu_enabled(setting_enable_commands, Where, Menu, + fun(Menu1) -> Menu1 ++ from_script_menu(Mode) end); +menu({edit,plugin_preferences}, Menu) -> + Menu ++ [{?__(1,"Scripts Preference"), shapes_from_scripts_preference}]; +menu({help}=Where, Menu) -> + menu_enabled(Where, Menu, fun(Menu1) -> help_script_menu(Menu1) end); +menu(Where,Menu) -> + %% Menu items are added from plugin scripts in other submenus. + menu_enabled(Where, Menu, fun(Menu1) -> Menu1 end). + + +%% Append menus generated for plugin scripts and script folders. +menu_1(Where,Menu) -> + [{plugin_scripts, L}] = ets:lookup(script_eng, plugin_scripts), + pluginscripts_make_menu(Where, Menu, L). + + +from_script_menu(shape) -> + [{?__(1,"Shape from Script"), + {shape_from_script, mouse_choice()}}]; +from_script_menu(import) -> + [{?__(2,"Script-based Importers..."), + {import_export_from_script, select}}]; +from_script_menu(export) -> + [{?__(3,"Script-based Exporters..."), + {import_export_from_script, select}}]; +from_script_menu(_Mode) -> + [{?__(4,"Script-based Commands..."), + {command_from_script, select}}]. + +help_script_menu(Menu) -> + lists:reverse(help_script_menu(2, lists:reverse(Menu))). + +help_script_menu(0, Menu) -> + [{?__(1,"Using Scripts"), {help_info_script, [ + {?__(2,"Scripting Information"),script_basics, + ?__(2,"Scripting Information")}, + {?__(3,"Scripting Reference"),scripting_reference, + ?__(3,"Scripting Reference")} + ]}}] ++ Menu; +help_script_menu(_, []) -> []; %% This shouldn't happen. +help_script_menu(N, [Item | Menu]) + when Item =/= separator, N > 0 -> + [Item | help_script_menu(N, Menu)]; +help_script_menu(N, [separator | Menu]) + when N > 0 -> + [separator | help_script_menu(N-1, Menu)]. + + + +mouse_choice() -> + fun (help,_) -> {?__(1,"From Script"), [], []}; + (1,_) -> {shape, {shape_from_script, select}}; + (2,_) -> ignore; + (3,_) -> {shape, {shape_from_script, select}}; + (_,_) -> ignore + end. + + +command({file, {import, {import_export_from_script, select}}}, fetch_props) -> + %% TODO: Wings sends this to find out which importer can handle a given file extension. + {[], fun (_) -> keep end}; +command({file,{Op,{import_export_from_script, select}}}, St) + when Op =:= import; + Op =:= export; + Op =:= export_selected -> + case Op of + import -> ScrTyp = import; + _ -> ScrTyp = export + end, + cmd_select_from_list(setting_paths_import_export, ScrTyp, + from_script_fun(Op, St)); +command({file,{Op,{import_export_from_script, {#command_rec{}=CommandRec, Params}}}}, St) + when Op =:= import; + Op =:= export; + Op =:= export_selected -> + Fun=from_script_fun(Op, St), + Fun(Params, CommandRec); + + +command({shape, {shape_from_script, select}}, St) -> + cmd_select_from_list(setting_paths_shapes, false, + from_script_fun(shape, St)); +command({shape, {shape_from_script, {#command_rec{}=CommandRec, Params}}}, St) -> + Fun=from_script_fun(shape, St), + Fun(Params, CommandRec); + + +command({Op, {command_from_script, select}}, St) + when Op =:= vertex; Op =:= edge; Op =:= face; Op =:= body -> + cmd_select_from_list(setting_paths_commands, Op, + from_script_fun(Op, St)); +command({Op, {command_from_script, {#command_rec{}=CommandRec, Params}}}, St) -> + Fun=from_script_fun(Op, St), + Fun(Params, CommandRec); + +command({edit, {plugin_preferences, shapes_from_scripts_preference}}, St) -> + dlg_script_preference(St); + +command({help, {help_info_script, script_basics}}, _St) -> + {Title, Text} = help_information(script_basics), + wings_dialog:info(Title, Text, []); +command({help, {help_info_script, scripting_reference}}, _St) -> + wings_dialog:manual(filename:join( + init_dir(), + "scripting-reference-:LANG:.manual")), + keep; + +%% Command is searched among plugin scripts and returns 'next' +%% if nothing is found. +command(Cmd, St) -> + [{plugin_scripts, L}] = ets:lookup(script_eng, plugin_scripts), + pluginscripts_command(Cmd,St,L). + + + +installing_archive(plugin_script, _List) -> + {"plugin_scripts", ?__(1,"plugin script"), + fun (_Dest) -> ok end}; +installing_archive(_, _List) -> + next. + + +%%% +%%% + + +from_script_fun(Op, St) + when Op =:= import; + Op =:= export; + Op =:= export_selected -> + fun (Params, CR) -> + import_export_from_script(Op, Params, CR, St) + end; +from_script_fun(Op, St) + when Op =:= shape -> + fun (Params, CR) -> + make_shape_from_script(Params, CR, St) + end; +from_script_fun(Op, St) -> + fun(Params, CR) -> + command_from_script(Op, Params, CR, St) + end. + + +cmd_select_from_list(DirsSetting, ScrOp, F) -> + ScriptDirs = script_dirs(DirsSetting), + case scripts_menu_select_plugin(ScriptDirs, ScrOp) of + {load_script, Script} -> + select_script(Script, F); + _ -> + keep + end. + +select_script({ScriptFile, ScriptType, WSCRFile}, F) + when is_list(ScriptFile) -> + Params = true, + case read_wscr_file_content(WSCRFile) of + {ok, WSCRContent} -> + F(Params, #command_rec{ + wscrcont=WSCRContent, + scrfile=ScriptFile, + scrtype=ScriptType}); + _ -> + keep + end; +select_script(false, _F) -> + keep. + +script_info(WSCRFile, shape) -> + script_info(WSCRFile, false); +script_info(WSCRFile, export_selected) -> + script_info(WSCRFile, export_selected); +script_info(WSCRFile, Op) -> + case read_script_info_file(WSCRFile, Op) of + {ok, {ScriptFile, ScriptType, _, _}} when is_list(ScriptFile), is_list(ScriptType) -> + {ScriptFile, ScriptType, WSCRFile}; + _ -> + false + end. + +%% Gets a list of callable functions, which also provides the dynamic +%% type information for each argument, and the return types. +%% +get_callable() -> + InitScriptsDir = filename:absname(code:where_is_file(init_dir())), + {ok,[L]} = file:consult(filename:join(InitScriptsDir,"callable.conf")), + maps:from_list(L). + + +%% Very basic information for script users. +help_information(script_basics) -> + { + ?__(1,"Scripting Information"), + [ + ?__(2,"Scripting Information\n\n" + "Scripts are small programs that can be used to create new " + "shapes, importers, exporters and commands. Before " + "scripts can be used, the scripting interpreters needs to be " + "installed and its path set in 'Scripting Preference'.\n\n" + "The two scripting languages that are available are Python " + "and Scheme.\n\n" + "Each script needs the script itself (.py or .scm) and " + "a .wscr file with the same name before the extension for " + "the script to work.") + ] + }. + +askdialog(Ask, Title, ParamList, Templates, F) -> + Dialog = [ askdialog_e(B) || B <- ParamList ] ++ + lists:append([askdialog_extra(T) || T <- Templates]), + wpa:dialog(Ask, Title, Dialog, F). + +askdialog_w_prev(Ask, Title, ParamList, Templates, F, St, FP) -> + erlang:put(scripting_shape_fun, FP), + Dialog = [ askdialog_e(B) || B <- ParamList ] ++ + lists:append([askdialog_extra(T) || T <- Templates]), + Fun = fun({dialog_preview,Params}) -> + {preview,F(Params),St}; + (cancel) -> + FP(close), + erlang:erase(scripting_shape_fun), + erlang:erase(scripting_callable), + St; + (Params) -> + erlang:put(scripting_shape_fun_close, true), + {commit,F(Params),St} + end, + wings_dialog:dialog(Ask, Title, {preview, Dialog}, Fun). + + +askdialog_extra({import, Opts}) -> + [wpa:dialog_template(?MODULE, import, Opts)]; +askdialog_extra({export, Opts}) -> + [wpa:dialog_template(?MODULE, export, Opts)]. + +askdialog_e({Text, Number}) + when is_list(Text) -> + {hframe,[{label,Text},{text,Number}]}; +askdialog_e({Text, Number, C}) + when is_list(Text) -> + {hframe,[{label,Text},{text,Number,C}]}; +askdialog_e({menu,{menu,List},Text,Default}) -> + {hframe,[{label,Text},{menu,List,askdialog_e_mval(Default,List),[]}]}; +askdialog_e({menu,{menu,List},Text,Default,C}) -> + {hframe,[{label,Text},{menu,List,askdialog_e_mval(Default,List),C}]}; +askdialog_e({menu,{vradio,List},Text,Default}) -> + {hframe,[{label,Text},{vradio,List,askdialog_e_mval(Default,List)}]}; +askdialog_e({menu,{vradio,List},Text,Default,C}) -> + {hframe,[{label,Text},{vradio,List,askdialog_e_mval(Default,List),C}]}; +askdialog_e({menu,{hradio,List},Text,Default}) -> + {hframe,[{label,Text},{hradio,List,askdialog_e_mval(Default,List)}]}; +askdialog_e({menu,{hradio,List},Text,Default,C}) -> + {hframe,[{label,Text},{hradio,List,askdialog_e_mval(Default,List),C}]}; +askdialog_e({checkbox,_,Text,Default}) -> + {Text, Default}; +askdialog_e({checkbox,_,Text,Default,C}) -> + {Text, Default, C}; +askdialog_e({browse,_,Text,Default}) -> + {hframe,[{label,Text},{button,{text,askdialog_e_bval(Default),[]}}]}; +askdialog_e({browse,_,Text,Default,C}) -> + {hframe,[{label,Text},{button,{text,askdialog_e_bval(Default),C}}]}; +askdialog_e({adv_params,_,_}=Tuple) -> + advp_merge_vals(Tuple). + +%% Adjust the default value if it doesn't appear in the +%% list. +%% +askdialog_e_mval(Default,List) -> + Atoms = [V || {_,V} <- List], + case lists:member(Default, Atoms) of + true -> + Default; + _ -> + [V1|_] = Atoms, + V1 + end. + +%% Adjust the browse default value if it isn't a string. +%% +askdialog_e_bval(Str) + when is_binary(Str) -> + binary_to_list(Str); +askdialog_e_bval(Str) + when is_list(Str) -> + binary_to_list(iolist_to_binary(Str)); +askdialog_e_bval(_) -> + "". + +%% Merge list of values with adv_param dialog term. +%% +advp_merge_vals({adv_params,Tuple,Vals}) -> + advp_m_vals(Tuple,maps:from_list(Vals)). +advp_m_vals(List,Vals) + when is_list(List)-> + [ advp_m_vals(A,Vals) || A <- List]; +advp_m_vals(Tuple,Vals) + when is_tuple(Tuple)-> + List = tuple_to_list(Tuple), + list_to_tuple([ advp_m_vals(A,Vals) || A <- List]); +advp_m_vals(Atom,Vals) + when is_atom(Atom) -> + case maps:find(Atom,Vals) of + {ok, Val} -> Val; + _ -> Atom + end; +advp_m_vals(Anything,_Vals) -> + Anything. + + +%% Start and run a script and get the return value +%% +run_script_once(DefaultReturn, Vars) when is_map(Vars) -> + #{script_type:=ScriptType, + script_filename:=ScriptFileName, + script_params:=ScriptParams, + more_params:=MoreParams, + settings:=Settings}=Vars, + case erlang:get(scripting_shape_fun) of + FP when is_function(FP) -> + Ret = FP({run, ScriptParams, MoreParams, DefaultReturn, Vars}), + case erlang:get(scripting_shape_fun_close) of + true -> + FP(close), + erlang:erase(scripting_shape_fun_close), + erlang:erase(scripting_shape_fun), + erlang:erase(scripting_callable); + _ -> + ok + end, + Ret; + undefined -> + case run_script_w_preview(ScriptType, ScriptFileName, Settings) of + {error, Err} -> {error, Err}; + {ok, F} when is_function(F) -> + Ret = F({run, ScriptParams, MoreParams, DefaultReturn, Vars}), + F(close), + Ret + end + end. + + +%% Start a script but return a function to run with parameters +%% +run_script_w_preview(ScriptType, ScriptFileName, Settings) + when is_list(ScriptFileName) -> + {ok, StrList} = load_lang_file(ScriptFileName, current_lang_code()), + erlang:put(scripting_callable, get_callable()), + case get_script_pid(ScriptType, Settings) of + {ok, PID} -> + PID ! {load_script, ScriptFileName, StrList, self()}, + run_script_w_preview_1(PID); + {error, Err} -> + {error, Err} + end; +run_script_w_preview(_ScriptType, none, _Settings) -> + {error, ?__(1, "Script file not found, check if script has the same name " + "as the .wscr file, and 'type' matches script file type.")}. + +run_script_w_preview_1(PID) -> + case run_script_getting_data_once() of + {[[ok|_]|_], _} -> + PID ! next, + {ok, run_script_w_preview_1_fun(PID)}; + Returned -> + err(io_lib:format(?__(2,"Returned: ") ++ "run_script_1=~p", [Returned])), + {error, {unexpected, Returned}} + end. +run_script_w_preview_1_fun(PID) -> + fun + ({run, ScriptParams, MoreParams, DefaultReturn, TempVars}) -> + run_script_1(ScriptParams, MoreParams, DefaultReturn, PID, TempVars); + (close) -> + PID ! {close, self()}, + receive + exited -> ok + after 20 -> + err(?__(1,"Did not recv 'exited'")), + ok + end + end. + + +run_script_1(ScriptParams, MoreParams, DefaultReturn, PID, TempVars) -> + ShowTupleDebug = wpa:pref_get(?MODULE, setting_show_tuple, false), + PID ! {run_script, ScriptParams, MoreParams, self()}, + case run_script_getting_data_until_ret(DefaultReturn, TempVars) of + {error, _} -> {error, ?__(2,"No results")}; + {keep, _} -> {ok, {keep, #{}}}; + {{[ReturnList], _}, RetTempVars_0} -> + RetTempVars = RetTempVars_0#{ + script_type=>none, + script_filename=>none, + script_params=>none, + more_params=>none, + settings=>none}, + PID ! next, + Tuplefied = run_script_tuplefy(ReturnList), + if ShowTupleDebug =:= true -> + io:format(?__(1,"Tuple:") ++ "~p~n", [Tuplefied]); + true -> ok + end, + {ok, {Tuplefied, RetTempVars}} + end. + + +run_script_getting_data_once() -> + ShowTupleDebug = wpa:pref_get(?MODULE, setting_show_tuple, false), + run_script_getting_data_once(#crun_state{}, ShowTupleDebug). +run_script_getting_data_once(QueryState, ShowTupleDebug) -> + receive + {reply, SendPID, {[[{atom, <<"%",_/binary>>} | _] | _]=Ret_1, _}} + when is_pid(SendPID) -> + QueryState_1 = run_script_getting_data_special( + QueryState, ShowTupleDebug, + SendPID, Ret_1), + run_script_getting_data_once(QueryState_1, ShowTupleDebug); + {reply, SendPID, Ret} + when is_pid(SendPID) -> + ?DEBUG_FMT("reply ~w~n",[Ret]), + Ret; + exited -> + error(exited) + end. + +run_script_getting_data_until_ret(CurrentReturn, TempVars) -> + run_script_getting_data_until_exit(CurrentReturn, TempVars). + +run_script_getting_data_until_exit(CurrentReturn, TempVars) -> + ShowTupleDebug = wpa:pref_get(?MODULE, setting_show_tuple, false), + ScrSt = #crun_state{temp_vars=TempVars}, + {Ret, #crun_state{temp_vars=TempVars_1}=_ScrSt_1} = + run_script_getting_data_until_exit(CurrentReturn, ScrSt, ShowTupleDebug), + {Ret, TempVars_1}. +run_script_getting_data_until_exit(CurrentReturn, QueryState, ShowTupleDebug) -> + receive + {reply, SendPID, {[[{atom, <<"%ok">>} | _] | _], _}} + when is_pid(SendPID) -> + ?DEBUG_FMT("got ok return~n",[]), + {CurrentReturn, QueryState}; + {reply, SendPID, {[[{atom, <<"%",_/binary>>} | _] | _]=Ret_1, _}} + when is_pid(SendPID) -> + QueryState_1 = run_script_getting_data_special( + QueryState, ShowTupleDebug, + SendPID, Ret_1), + run_script_getting_data_until_exit(CurrentReturn, QueryState_1, ShowTupleDebug); + {reply, SendPID, Ret} + when is_pid(SendPID) -> + ?DEBUG_FMT("reply ~w~n",[Ret]), + run_script_getting_data_until_exit(Ret, QueryState, ShowTupleDebug); + exited -> + ?DEBUG_FMT("exited~n",[]), + {CurrentReturn, QueryState} + end. + +run_script_getting_data_special(QueryState, ShowTupleDebug, SendPID, Ret_1) -> + run_script_getting_data_special_1(run_script_tuplefy(Ret_1), + QueryState, ShowTupleDebug, SendPID). + +%% Set a query variable +run_script_getting_data_special_1([['%setvar', VarName, VarValue]|_], + QueryState, ShowTupleDebug, SendPID) -> + {Ret,QueryState_1} = set_var(VarName, VarValue, QueryState), + if ShowTupleDebug =:= true -> + io:format("setvar: ~s = ~w~n", [VarName, VarValue]); + true -> ok + end, + SendPID ! {reply_to_script, [Ret]}, + QueryState_1; + +%% Get a query variable +run_script_getting_data_special_1([['%getvar', VarName]|_], + QueryState, ShowTupleDebug, SendPID) -> + QueryState_1 = QueryState, + Ret = get_var(VarName, QueryState), + if ShowTupleDebug =:= true -> + io:format("getvar: ~s~n", [VarName]); + true -> ok + end, + SendPID ! {reply_to_script, [Ret]}, + QueryState_1; + +%% Do a query. +run_script_getting_data_special_1([['%query', QueryStr]|_], + QueryState, ShowTupleDebug, SendPID) + when is_list(QueryStr) -> + {QueryRes, QueryState_1} = crun_pv({etp,QueryStr}, QueryState, []), + if ShowTupleDebug =:= true -> + io:format("query: ~s -> ~p~n", [QueryStr, QueryRes]); + true -> ok + end, + SendPID ! {reply_to_script, [QueryRes]}, + QueryState_1; + +%% Call a we function with the current #we{} and get a return value +%% and change #we{} +run_script_getting_data_special_1([['%we!', [Mod,FunName|ApplyArgs]]|_], + QueryState, _ShowTupleDebug, SendPID) + when is_list(ApplyArgs) -> + {Ret,QueryState_1} = + change_we( + Mod,FunName,ApplyArgs, + QueryState,erlang:get(scripting_callable)), + SendPID ! {reply_to_script, Ret}, + QueryState_1; + +%% Call a we function with the current #we{} and get a return value +run_script_getting_data_special_1([['%we', [Mod,FunName|ApplyArgs]]|_], + QueryState, _ShowTupleDebug, SendPID) + when is_list(ApplyArgs) -> + QueryState_1 = QueryState, + Ret = val_from_we( + Mod,FunName,ApplyArgs, + QueryState,erlang:get(scripting_callable)), + SendPID ! {reply_to_script, [Ret]}, + QueryState_1; + +%% Call a we function with a previous #we{} in the temporary stack +%% and get a return value and change #we{} +run_script_getting_data_special_1([['%we-previous!', [StackIndex,Mod,FunName|ApplyArgs]]|_], + QueryState, _ShowTupleDebug, SendPID) + when is_list(ApplyArgs) -> + {Ret,QueryState_1} = + change_with_previous_we( + StackIndex,Mod,FunName,ApplyArgs, + QueryState,erlang:get(scripting_callable)), + SendPID ! {reply_to_script, Ret}, + QueryState_1; + +%% Call a we function with a previous #we{} in the temporary stack +%% and get a return value +run_script_getting_data_special_1([['%we-previous', [StackIndex,Mod,FunName|ApplyArgs]]|_], + QueryState, _ShowTupleDebug, SendPID) + when is_list(ApplyArgs) -> + QueryState_1 = QueryState, + Ret = val_from_previous_we( + StackIndex,Mod,FunName,ApplyArgs, + QueryState,erlang:get(scripting_callable)), + SendPID ! {reply_to_script, [Ret]}, + QueryState_1; + +%% Push a copy of the #we{} variable into the temporary stack +run_script_getting_data_special_1([['%push-we', _]|_], + #crun_state{temp_vars=TempVars}=QueryState, _ShowTupleDebug, SendPID) -> + We1 = etp_get_temp_var(we, TempVars), + WeStack = etp_get_temp_var(prev_we_stack, TempVars), + QueryState_1 = etp_store_temp(prev_we_stack, [We1|WeStack], QueryState), + SendPID ! {reply_to_script, [0]}, + QueryState_1; + +%% Pop the most recent #we{} from the temporary stack +run_script_getting_data_special_1([['%pop-we', [How|_Args]]|_], + #crun_state{temp_vars=TempVars}=QueryState, _ShowTupleDebug, SendPID) -> + case How of + "discard" -> ok; + _ -> ok + end, + [_|WeStack] = etp_get_temp_var(prev_we_stack, TempVars), + QueryState_1 = etp_store_temp(prev_we_stack, WeStack, QueryState), + SendPID ! {reply_to_script, [0]}, + QueryState_1; + + +%% An intermittent message sent to prevent the scripting plugin from +%% thinking the script has stalled and timing out. +run_script_getting_data_special_1([['%keepalive', _Number]|_], + QueryState, _ShowTupleDebug, _SendPID) -> + QueryState_1 = QueryState, + QueryState_1; + +%% Send a progress bar message to the plugin to update the user +%% on what the script is doing. +run_script_getting_data_special_1([['%pbmessage', Percent, Str]|_], + QueryState, _ShowTupleDebug, _SendPID) + when is_float(Percent), is_list(Str) -> + wings_pb:update(Percent, Str), + wings_pb:pause(), + QueryState_1 = QueryState, + QueryState_1; + +%% Display a panel window with a text box that shows the results of +%% processing to the user. For example, a script that analyzes the +%% model and displays statistics. +run_script_getting_data_special_1([['%resulttext', Text]|_], + QueryState, _ShowTupleDebug, _SendPID) + when is_list(Text) -> + info_dialog(Text), + QueryState_1 = QueryState, + QueryState_1; +run_script_getting_data_special_1([['%resulttext', Text, OptList]|_], + QueryState, _ShowTupleDebug, _SendPID) + when is_list(Text), is_list(OptList) -> + info_dialog(Text), + QueryState_1 = QueryState, + QueryState_1; + +run_script_getting_data_special_1(_, QueryState, _ShowTupleDebug, SendPID) -> + QueryState_1 = QueryState, + SendPID ! {reply_to_script, [error]}, + QueryState_1. + + + + +%% Some variables such as 'we' are special and should not be set by +%% the script to any kind of data. Changes to 'we' should be done with +%% the '%we!' command. +set_var(we, _, QueryState) -> + {read_only, QueryState}; +set_var(VarName, VarValue, QueryState) + when is_atom(VarName) -> + {ok, etp_store_temp(VarName, VarValue, QueryState)}; +set_var(VarName, VarValue, QueryState) + when is_list(VarName) -> + {ok, etp_store_temp(binstr(VarName), VarValue, QueryState)}. + +get_var(VarName, #crun_state{temp_vars=TempVars}=_) + when is_atom(VarName) -> + etp_get_temp_var(VarName, TempVars); +get_var(VarName, #crun_state{temp_vars=TempVars}=_) + when is_list(VarName) -> + etp_get_temp_var(binstr(VarName), TempVars). + + +%% Change #we{} by calling a function with given arguments +change_we(Mod,FunName,ApplyArgs0,#crun_state{temp_vars=TempVars}=QueryState,Callables) -> + Ret0 = apply_fun(Mod,FunName,ApplyArgs0,TempVars,Callables), + case Ret0 of + #we{}=We1 -> + QueryState_1 = etp_store_temp(we, We1, QueryState), + {[],QueryState_1}; + [_|_]=Ret1 -> + {[We2], Ret2} = lists:partition(fun (#we{}) -> true; (_) -> false end, Ret1), + QueryState_1 = etp_store_temp(we, We2, QueryState), + {Ret2,QueryState_1} + end. + +%% Get a value from #we{} +val_from_we(Mod,FunName,ApplyArgs0,#crun_state{temp_vars=TempVars}=_,Callables) -> + apply_fun(Mod,FunName,ApplyArgs0,TempVars,Callables). + +apply_fun(Mod,FunName,ApplyArgs0,TempVars,Callables) -> + {ApplyArgs,RetTypes} = apply_args_we({Mod,FunName,length(ApplyArgs0)}, ApplyArgs0, TempVars,Callables), + returned_vals(erlang:apply(Mod,FunName,ApplyArgs),RetTypes). + + + +%% Change current #we{} by calling a function with given arguments and previous we on stack +change_with_previous_we(StackIndex,Mod,FunName,ApplyArgs0,#crun_state{temp_vars=TempVars}=QueryState,Callables) -> + Ret0 = tempvars_with_previous_we( + fun (TempVars1) -> + apply_fun(Mod,FunName,ApplyArgs0,TempVars1,Callables) + end, TempVars, StackIndex), + case Ret0 of + #we{}=We1 -> + QueryState_1 = etp_store_temp(we, We1, QueryState), + {[],QueryState_1}; + [_|_]=Ret1 -> + {[We2], Ret2} = lists:partition(fun (#we{}) -> true; (_) -> false end, Ret1), + QueryState_1 = etp_store_temp(we, We2, QueryState), + {Ret2,QueryState_1} + end. + +%% Get a value from previous #we{} in stack +val_from_previous_we(StackIndex,Mod,FunName,ApplyArgs0,#crun_state{temp_vars=TempVars}=_,Callables) -> + tempvars_with_previous_we( + fun (TempVars1) -> + apply_fun(Mod,FunName,ApplyArgs0,TempVars1,Callables) + end, TempVars, StackIndex). + + +tempvars_with_previous_we(Fun, TempVars, StackIndex) + when is_map(TempVars), is_integer(StackIndex) -> + WeStack = maps:get(prev_we_stack, TempVars), + We1 = lists:nth(StackIndex, WeStack), + Fun(TempVars#{we=>We1}). + + +returned_vals(Tuple, [_|_]=Types) -> + returned_vals_1(tuple_to_list(Tuple), Types); +returned_vals(Ret, Type) -> + returned_vals_2(Ret, Type). + +returned_vals_1([Ret|List], [Type|Types]) -> + [returned_vals_2(Ret, Type)|returned_vals_1(List, Types)]; +returned_vals_1([], []) -> + []. + +returned_vals_2(Ret, false) -> + Ret; +returned_vals_2(Ret, gbset) -> + gb_sets:to_list(Ret); +returned_vals_2(Ret, gbtree) -> + gb_trees:to_list(Ret); +returned_vals_2(Ret, _Type) -> + Ret. + + +%% Add #we{} into argument list. +apply_args_we(ModFun, ApplyArgs0, TempVars, Callables) -> + {ArgTypes,ReturnTypes} = case maps:find(ModFun, Callables) of + {ok,ArgRet} -> ArgRet; + _ -> {false,list} + end, + Args = apply_args_we_1(ArgTypes, ApplyArgs0, TempVars), + {Args, ReturnTypes}. +apply_args_we_1(false, ApplyArgs0, TempVars) -> + We0 = etp_get_temp_var(we, TempVars), + lists:map(fun + (we) -> We0; + ([list_to_gbset, V]) -> gb_sets:from_list(V); + ([gbset_to_list, V]) -> gb_sets:to_list(V); + (B) -> B + end, ApplyArgs0); +apply_args_we_1(List, ApplyArgs0, TempVars) -> + apply_args_we_2(List, ApplyArgs0, TempVars, []). + +apply_args_we_2([], _ApplyArgs0, _TempVars, OL) -> + lists:reverse(OL); +apply_args_we_2([we|List], ApplyArgs0, TempVars, OL) -> + We0 = etp_get_temp_var(we, TempVars), + apply_args_we_2(List, ApplyArgs0, TempVars, [We0|OL]); +apply_args_we_2([st|List], ApplyArgs0, TempVars, OL) -> + St0 = etp_get_temp_var(st, TempVars), + apply_args_we_2(List, ApplyArgs0, TempVars, [St0|OL]); +apply_args_we_2([Type|List], [Arg|ApplyArgs0], TempVars, OL) -> + Arg_1 = case Type of + atom when is_atom(Arg) -> Arg; + atom when is_list(Arg) -> list_to_atom(Arg); + float when is_number(Arg) -> float(Arg); + gbset -> gb_sets:from_list(Arg); + gbtree -> gb_trees:from_orddict(orddict:from_list(Arg)); + int when is_integer(Arg) -> Arg; + int when is_number(Arg) -> round(Arg); + list when is_list(Arg) -> Arg; + matrix when is_atom(Arg) -> Arg; + matrix -> + Arg_2 = if is_list(Arg) -> list_to_tuple(Arg); true -> Arg end, + case Arg_2 of + {A11,A21,A31, + A12,A22,A32, + A13,A23,A33, + A14,A24,A34} -> + {float(A11),float(A21),float(A31), + float(A12),float(A22),float(A32), + float(A13),float(A23),float(A33), + float(A14),float(A24),float(A34)}; + {A11,A21,A31,A41, + A12,A22,A32,A42, + A13,A23,A33,A43, + A14,A24,A34,A44} -> + {float(A11),float(A21),float(A31),float(A41), + float(A12),float(A22),float(A32),float(A42), + float(A13),float(A23),float(A33),float(A43), + float(A14),float(A24),float(A34),float(A44)} + end; + ordset when is_list(Arg) -> ordsets:from_list(Arg); + val -> Arg; + vec2 -> + case Arg of + {X,Y} when is_number(X),is_number(Y) -> + {float(X),float(Y)}; + [X,Y] when is_number(X),is_number(Y) -> + {float(X),float(Y)} + end; + vec3 -> + case Arg of + {X,Y,Z} when is_number(X),is_number(Y),is_number(Z) -> + {float(X),float(Y),float(Z)}; + [X,Y,Z] when is_number(X),is_number(Y),is_number(Z) -> + {float(X),float(Y),float(Z)} + end; + vec3_or_atom -> + case Arg of + {X,Y,Z} when is_number(X),is_number(Y),is_number(Z) -> + {float(X),float(Y),float(Z)}; + [X,Y,Z] when is_number(X),is_number(Y),is_number(Z) -> + {float(X),float(Y),float(Z)}; + _ when is_atom(Arg) -> + Arg + end + end, + apply_args_we_2(List, ApplyArgs0, TempVars, [Arg_1|OL]). + + + +info_dialog(List) -> + info_dialog(?__(1,"Info"), List). +info_dialog(Title, [Str|_]=List) + when is_list(Str) -> + wings_dialog:info(Title, List, []); +info_dialog(Title, [C|_]=Str) + when is_integer(C) -> + info_dialog(Title, [Str]). + + +run_script_tuplefy({string, BString1}) + when is_binary(BString1) -> + unbinstr(BString1); +run_script_tuplefy({{atom, Atom1},Val}) + when is_binary(Atom1) -> + {list_to_atom(unbinstr(Atom1)), run_script_tuplefy(Val)}; +run_script_tuplefy(['!list']) -> + []; +%% Specifies this is actually a list of atoms. +run_script_tuplefy(['!list', Atom | Everything]) + when is_atom(Atom) -> + [Atom | run_script_tuplefy_args(Everything)]; +run_script_tuplefy([ok, Tuple]) + when is_list(Tuple) -> + {ok, run_script_tuplefy(Tuple)}; +run_script_tuplefy([TupleName | Everything]) + when is_atom(TupleName) -> + case is_wings_rec(TupleName, length(Everything)) of + true -> + list_to_tuple([TupleName | run_script_tuplefy_args(Everything)]); + false -> + [TupleName | run_script_tuplefy_args(Everything)] + end; +run_script_tuplefy(Tuple) + when is_tuple(Tuple), + is_tuple(element(1, Tuple)), + element(1,element(1,Tuple)) =:= atom -> + run_script_tuplefy(tuple_to_list(Tuple)); +run_script_tuplefy([TupleName | Everything]) + when is_binary(TupleName) -> + list_to_tuple( + [list_to_atom(unbinstr(TupleName)) + | run_script_tuplefy_args(Everything)]); +run_script_tuplefy([NonSymbol | _]=List) + when is_number(NonSymbol); + is_tuple(NonSymbol); + is_list(NonSymbol) -> + lists:map(fun(A1) -> run_script_tuplefy(A1) end, List); +run_script_tuplefy({atom, Atom1}) + when is_binary(Atom1) -> + list_to_atom(unbinstr(Atom1)); +run_script_tuplefy(Unk) -> + Unk. +run_script_tuplefy_args([]) -> []; +run_script_tuplefy_args([A | R]) -> + [run_script_tuplefy(A) | run_script_tuplefy_args(R)]. + + +%% Test if a given atom and a length means this might be a list +%% that represents a record. +%% +is_wings_rec(e3d_transf, 2) -> true; +is_wings_rec(e3d_face, 7) -> true; +is_wings_rec(e3d_mesh, 8) -> true; +is_wings_rec(e3d_object, 4) -> true; +is_wings_rec(e3d_file, 4) -> true; +is_wings_rec(e3d_image, 10) -> true; + +is_wings_rec(new_shape, 3) -> true; + +is_wings_rec(_, _) -> false. + +%% Get the process id of the current interpreter handling process. +%% +get_script_pid(ScriptType_S, Settings) -> + RunnerPIDAtom = list_to_atom( + "shape_from_scripts_script_runner_" ++ ScriptType_S), + case whereis(RunnerPIDAtom) of + undefined -> + %% The init folder contain code that runs before our scripts + InitScriptsDir = filename:absname(code:where_is_file(init_dir())), + {Interpreter, Arguments, InitFile} = + scripting_engines:script_interp(ScriptType_S, Settings, InitScriptsDir), + case Interpreter of + none -> + {error, no_interpreter}; + _ -> + case os:find_executable(Interpreter) of + false -> + {error, interpreter_not_found}; + InterpreterFullPath -> + WingsLang = erlang:get(wings_lang), %% for wings_lang + PIDNew = spawn(fun () -> + erlang:put(wings_lang,WingsLang), + run_script_runner(InterpreterFullPath, Arguments, InitFile) + end), + register(RunnerPIDAtom, PIDNew), + {ok, PIDNew} + end + end; + PID -> + {ok, PID} + end. + +%% This runs from the interpreter handling process. +%% +run_script_runner(Interpreter, Arguments, InitFile) -> + try Port = + open_port( + {spawn_executable, Interpreter}, + [exit_status, + {args, Arguments ++ [InitFile]}, + {line, 64000}, + binary, + stderr_to_stdout, + use_stdio, + hide]), + port_connect(Port, self()), + run_script_runner_loop(Port) + catch + error:enoent -> + err(io_lib:format(?__(1,"could not run script interpreter:")++" ~s", + [Interpreter])) + end. +run_script_runner_loop(Port) -> + receive + {load_script, Script, StrList, RetPID} -> + send_to_scr_port(Port, [ + {atom, "run_init"}, + {string, Script}, + prepare_string_pairs(StrList)], RetPID), + run_script_runner_inner_loop(Port, RetPID, false, []); + + {run_script, Params, MoreParams, RetPID} -> + ?DEBUG_FMT(" *** ~p~n", [prepare_more_parameters(MoreParams)]), + send_to_scr_port(Port, [ + {atom, "run"}, + {string, ""}, + prepare_parameter_list_for_scm(Params), + prepare_more_parameters(MoreParams)], RetPID), + run_script_runner_inner_loop(Port, RetPID, false, []); + {close, PID} -> + port_close(Port), + PID ! exited; + next -> + run_script_runner_loop(Port); + M -> + io:format(?__(1,"~p: Unexpected ret: ~p~n"), [?MODULE,M]), + run_script_runner_loop(Port) + end. +run_script_runner_inner_loop(Port, RetPID, StartedL, LAcc) -> + receive + next -> + %% Go back to init commands + run_script_runner_loop(Port); + + {close, _} -> + port_close(Port), + RetPID ! exited; + + {reply_to_script, Tuple} -> + ?DEBUG_FMT("sending: ~p~n", [Tuple]), + try + OutP = iolist_to_binary([ + write_scm(prepare_more_parameters_r(Tuple)), <<"\n">>]), + ?DEBUG_FMT(" ** ~p~n", [OutP]), + port_command(Port, OutP) + catch + _:Error:StTr -> + err(io_lib:format("~p~nStTr:~n~p", [Error, StTr])), + port_command(Port, <<"(error output_error)">>) + end, + run_script_runner_inner_loop(Port, RetPID, StartedL, LAcc); + {Port, {data, SReply0}} -> + case SReply0 of + %% Shorter lines + {eol, <<>>} when StartedL =:= false -> + run_script_runner_inner_loop(Port, RetPID, StartedL, LAcc); + {eol, <<$(, _/binary>>=SReply} when StartedL =:= false -> + ?DEBUG_FMT("Data ~s~n", [SReply]), + RetPID ! {reply, self(), scm_parse(SReply)}, + run_script_runner_inner_loop(Port, RetPID, false, []); + {eol, SReply} when StartedL =:= false -> + scr_debug(SReply), + run_script_runner_inner_loop(Port, RetPID, false, []); + + %% Longer lines are split into chunks + {eol, SReply} when StartedL =:= true -> + case LAcc of + debug -> + scr_debug(SReply), + run_script_runner_inner_loop(Port, RetPID, false, []); + + _ -> + SReply_1 = iolist_to_binary(lists:reverse([SReply | LAcc])), + ?DEBUG_FMT("Data ~p~n", [SReply_1]), + RetPID ! {reply, self(), scm_parse(SReply_1)}, + run_script_runner_inner_loop(Port, RetPID, false, []) + end; + {noeol, <<$(, _/binary>>=SReply} when StartedL =:= false -> + run_script_runner_inner_loop(Port, RetPID, true, [SReply | LAcc]); + {noeol, _} when StartedL =:= false -> + run_script_runner_inner_loop(Port, RetPID, true, debug); + {noeol, SReply} when StartedL =:= true -> + case LAcc of + debug -> + scr_debug(SReply), + run_script_runner_inner_loop(Port, RetPID, StartedL, LAcc); + _ -> + run_script_runner_inner_loop(Port, RetPID, true, [SReply | LAcc]) + end; + + %% Uncertain data + _ -> + run_script_runner_inner_loop(Port, RetPID, StartedL, LAcc) + end; + {Port, {exit_status, ExitStatus}} -> + case ExitStatus of + 0 -> ok; + _ -> io:format(?__(1,"NOTE: Script runtime exited with code:") + ++ " ~w~n", [ExitStatus]) + end, + RetPID ! exited; + Unk -> + io:format("~p: Unexpected ret: ~p~n", [?MODULE, Unk]), + run_script_runner_inner_loop(Port, RetPID, StartedL, LAcc) + after 60000 -> + io:format(?__(2, "NOTE: Closing script due to timeout") ++ "~n", []), + port_close(Port), + RetPID ! exited + end. +send_to_scr_port(Port, L, RetPID) -> + try + OutP = iolist_to_binary([write_scm(L), <<"\n">>]), + port_command(Port, OutP) + catch + _:Error:StTr -> + err(io_lib:format("~p~nStTr:~n~p", [Error, StTr])), + RetPID ! exited, + exit(error) + end. + + +%% +%% Select Script +%% + +-spec scripts_menu_select_plugin([file:filename_all()], wings_op_mode()) -> + any(). +scripts_menu_select_plugin(ScriptDirs, Op) -> + Parent = wings_dialog:get_dialog_parent(), + Result = init_dlg_select_script([ + {parent, Parent}, + {script_dirs, ScriptDirs}, + {op, Op} + ]), + Result. + +init_dlg_select_script(Config) -> wx:batch(fun() -> dlg_select_script_do_init(Config) end). +dlg_select_script_do_init(Config) -> + Parent = proplists:get_value(parent, Config), + ScriptDirs = proplists:get_value(script_dirs, Config), + Op = proplists:get_value(op, Config), + + ScriptTable = ets:new(temp, [public,ordered_set]), + + F = wxDialog:new(Parent, ?wxID_ANY, ?__(1, "Select Script"), + [ {size, {630, 380}}, {style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER} ]), + MainSizer = wxBoxSizer:new(?wxVERTICAL), + + Frameparts = wxPanel:new(F, []), + wxBoxSizer:add(MainSizer, Frameparts, + [{flag, ?wxEXPAND bor ?wxALL}, {border, 2}, {proportion, 1}]), + Frameparts_1_sizer = wxBoxSizer:new(?wxVERTICAL), + wxPanel:setSizer(Frameparts, Frameparts_1_sizer), + + Frameparts_0 = wxPanel:new(Frameparts, []), + wxBoxSizer:add(Frameparts_1_sizer, Frameparts_0, + [{flag, ?wxEXPAND bor ?wxALL}, {border, 1}, {proportion, 8}]), + Frameparts_0_sizer = wxBoxSizer:new(?wxVERTICAL), + wxPanel:setSizer(Frameparts_0, Frameparts_0_sizer), + + ScriptListPanel = wxPanel:new(Frameparts_0), + wxBoxSizer:add(Frameparts_0_sizer, ScriptListPanel, + [{flag, ?wxEXPAND bor ?wxALL}, {border, 0}, {proportion, 1}]), + ScriptListPanel_sizer = wxBoxSizer:new(?wxHORIZONTAL), + wxPanel:setSizer(ScriptListPanel, ScriptListPanel_sizer), + + ScriptList = wxTreeCtrl:new(ScriptListPanel, + [{id, ?wxID_ANY}, {style, ?wxTR_DEFAULT_STYLE bor ?wxTR_HIDE_ROOT}]), + wxBoxSizer:add(ScriptListPanel_sizer, ScriptList, + [{flag, ?wxEXPAND bor ?wxALL}, {border, 3}, {proportion, 10}]), + + ScriptListButtonbar = wxPanel:new(ScriptListPanel), + wxBoxSizer:add(ScriptListPanel_sizer, ScriptListButtonbar, + [{flag, ?wxALL}, {border, 3}, {proportion, 1}]), + ScriptListButtonbar_sizer = wxBoxSizer:new(?wxHORIZONTAL), + wxPanel:setSizer(ScriptListButtonbar, ScriptListButtonbar_sizer), + + Btn_Refresh_BitBit = wxBitmap:new( + ?WPC_SHAPE_FROM_SCRIPTS_BTN_REFRESH_BITS, + ?WPC_SHAPE_FROM_SCRIPTS_BTN_REFRESH_WIDTH, + ?WPC_SHAPE_FROM_SCRIPTS_BTN_REFRESH_HEIGHT, [{depth, 1}]), + wxBitmap:setMask(Btn_Refresh_BitBit, + wxMask:new(Btn_Refresh_BitBit, {255,255,255})), + Btnrefresh = wxBitmapButton:new(ScriptListButtonbar, ?wxID_ANY, Btn_Refresh_BitBit, [ {size, {36, 30}} ]), + wxWindow:setToolTip(Btnrefresh, ?__(2, "Refresh")), + wxBoxSizer:add(ScriptListButtonbar_sizer, Btnrefresh), + + + + wxWindow:connect(Btnrefresh, command_button_clicked, [{callback, + fun(#wx{event=#wxCommand{commandInt=_Int}}, _Obj) -> + dlg_select_script_refresh(ScriptDirs, Op, ScriptTable, ScriptList) + end}]), + + InfoBox = wxTextCtrl:new(Frameparts, ?wxID_ANY, + [{style, ?wxTE_RICH bor ?wxTE_MULTILINE bor ?wxTE_READONLY bor ?wxTE_BESTWRAP }]), + wxBoxSizer:add(Frameparts_1_sizer, InfoBox, + [{flag, ?wxEXPAND bor ?wxALL}, {border, 3}, {proportion, 1}]), + + {Btnokay, _BtnCancel} = dlg_button_bar({Frameparts, Frameparts_1_sizer}, F, ?__(3,"OK"), ?__(4,"Cancel")), + + wxDialog:setSizer(F, MainSizer), + wxBoxSizer:recalcSizes(Frameparts_1_sizer), + + wxWindow:connect(ScriptList, command_tree_sel_changed, [{callback, + fun(#wx{event=#wxTree{item=Indx}}, _Obj) -> + dlg_select_script_show_info(ScriptTable, InfoBox, Btnokay, Indx) + end}]), + wxWindow:connect(ScriptList, command_tree_item_activated, [{callback, + fun(#wx{event=#wxTree{item=_Indx}}, _Obj) -> + wxDialog:endModal(F, ?wxID_OK) + end}]), + + dlg_select_script_refresh(ScriptDirs, Op, ScriptTable, ScriptList), + + wxWindow:disable(Btnokay), + + catch wings_dialog:set_dialog_parent(F), + case wxDialog:showModal(F) of + ?wxID_OK -> + Indx = wxTreeCtrl:getSelection(ScriptList), + case ets:lookup(ScriptTable, {script, Indx}) of + [] -> Result = keep; + [{_, {_Name, ScriptFile, WSCRFile, ScriptType, _ScriptDesc}}] -> + Result = {load_script, {ScriptFile, ScriptType, WSCRFile}} + end; + + _ -> + Result = keep + end, + ets:delete(ScriptTable), + catch wings_dialog:reset_dialog_parent(F), + wxDialog:destroy(F), + Result. + + +%% Get a list of paths for scripts. +%% +script_dirs(DirsSetting) -> + Str = wpa:pref_get(?MODULE, DirsSetting, ""), + scripting_engines:extra_script_dirs() ++ + lists:filter(fun ("") -> false; (_) -> true end, + [ string:trim(A1) || A1 <- string:tokens(Str, "\r\n") ]). + + +%% Make paths shorter for the UI +%% +not_the_full_path(A) -> + B = lists:reverse(filename:split(A)), + case B of + ["" | R] -> C = R; + R -> C = R + end, + case C of + [Within1] -> + lists:flatten(io_lib:format("[~s]", [Within1])); + [Within1, _] -> + lists:flatten(io_lib:format("[~s]", [Within1])); + [Within1, Within2, _] -> + lists:flatten(io_lib:format("[~s] ~s", [Within2, Within1])); + [A1, A2 | R_2] -> + Within1 = A1, + Within2 = A2, + case lists:reverse(R_2) of + [_, R_3 | _] -> Within3 = R_3; + [R_3] -> Within3 = R_3 + end, + lists:flatten(io_lib:format("[~s] ~s/~s", [Within3, Within2, Within1])) + end. + +-spec dlg_select_script_refresh([file:filename_all()], wings_op_mode(), + any(), wx:wx_object()) -> ok. +dlg_select_script_refresh(ScriptDirs, Op, ScriptTable, ScriptList) -> + wxTreeCtrl:deleteAllItems(ScriptList), + ERoot = wxTreeCtrl:addRoot(ScriptList, "*"), + lists:foreach(fun({Directory, SList}) -> + Elm = script_list_adddir(ERoot, ScriptList, not_the_full_path(Directory)), + lists:foreach(fun({WSCRFile, {ScriptFile, ScriptType, ScriptName, ScriptDesc}}) -> + script_list_add( + ScriptTable, ScriptList, Elm, ScriptName, ScriptFile, + WSCRFile, ScriptType, ScriptDesc) + end, SList) + end, read_script_dirs(ScriptDirs, Op)), + ok. + + +script_list_adddir(ERoot, ScriptList, Name) -> + Elm = wxTreeCtrl:appendItem(ScriptList, ERoot, Name), + wxTreeCtrl:ensureVisible(ScriptList, Elm), + Elm. + +script_list_add(ScriptTable, ScriptList, ParentNode, Name, ScriptFile, WSCRFile, ScriptType, ScriptDesc) -> + Elm = wxTreeCtrl:appendItem(ScriptList, ParentNode, Name), + ets:insert(ScriptTable, {{script, Elm}, {Name, ScriptFile, WSCRFile, ScriptType, ScriptDesc}}). + +dlg_select_script_insert_txt(E, Txt) -> + Sty_nrm = wxTextAttr:new(), + Eb = wxTextCtrl:getLastPosition(E), + wxTextCtrl:appendText(E, Txt), + wxTextCtrl:setStyle(E, Eb, wxTextCtrl:getLastPosition(E), Sty_nrm), + wxTextCtrl:appendText(E, "\n"). + +dlg_select_script_display_about_details(InfoBox, Name, ScriptFile, _WSCRFile, ScriptType, ScriptDesc0) -> + wxTextCtrl:clear(InfoBox), + case ScriptDesc0 of + "" -> + ScriptDesc = Name; + _ -> + ScriptDesc = ScriptDesc0 + end, + Text = io_lib:format("~s~n~n~s [~s]~n", [ScriptDesc, ScriptFile, ScriptType]), + dlg_select_script_insert_txt(InfoBox, Text), + wxTextCtrl:setSelection(InfoBox, 0, 0). + +dlg_select_script_show_info(ScriptTable, InfoBox, Btnokay, Indx) -> + case ets:lookup(ScriptTable, {script, Indx}) of + [] -> + wxWindow:disable(Btnokay); + [{_, {Name, ScriptFile, WSCRFile, ScriptType, ScriptDesc}}] -> + wxWindow:enable(Btnokay), + dlg_select_script_display_about_details(InfoBox, Name, ScriptFile, WSCRFile, ScriptType, ScriptDesc) + end. + +-spec read_script_dirs([file:filename_all()], wings_op_mode()) -> + [{file:filename_all(), [file:filename_all()]}]. +read_script_dirs(ScriptDirs, shape) -> + read_script_dirs(ScriptDirs, false); +read_script_dirs(ScriptDirs, Op) -> + read_script_dirs(ScriptDirs, Op, []). +read_script_dirs([], _, OList) -> lists:reverse(OList); +read_script_dirs([Path | ScriptDirs], Op, OList) -> + case file:list_dir(Path) of + {ok, Filenames} -> + {ok, LResults} = iterate_filenames(Filenames, Path, Op); + _ -> + LResults = [] + end, + read_script_dirs(ScriptDirs, Op, [{Path, LResults} | OList]). + +-spec iterate_filenames([file:filename_all()], file:filename_all(), + wings_op_mode()) -> {ok, [file:filename_all()]}. +iterate_filenames([FlN | Filenames], Path, Op) -> + {ok, iterate_filenames([FlN | Filenames], Path, Op, [])}. +iterate_filenames([], _, _, OList) -> lists:reverse(OList); +iterate_filenames([FlN | Filenames], Path, Op, OList) -> + FullFilename = filename:join(Path, FlN), + OList_1 = case file:read_file_info(FullFilename) of + {ok, FileInfo} -> + iterate_filenames_1(element(3, FileInfo), Filenames, + Path, Op, OList, FlN, FullFilename); + _ -> + OList + end, + iterate_filenames(Filenames, Path, Op, OList_1). +iterate_filenames_1(directory, _Filenames, _Path, Op, OList, _FlN, FullFilename) -> + SubDir = FullFilename, + OList_1 = case file:list_dir(SubDir) of + {ok, SubDir_Filenames} -> + iterate_filenames(SubDir_Filenames, SubDir, Op, OList); + _ -> + OList + end, + OList_1; +iterate_filenames_1(_, _Filenames, _Path, Op, OList, FlN, FullFilename) -> + Extension = string:to_lower(filename:extension(FlN)), + case Extension of + ".wscr" -> + case read_script_info_file(FullFilename, Op) of + {ok, W} -> [{FullFilename, W} | OList]; + _ -> OList + end; + _ -> + OList + end. + + + +-spec read_script_info_file(file:filename_all(), wings_op_mode()) -> + error | {ok, {file:filename_all() | none, string() | none, string(), string()}}. +read_script_info_file(FlN, Op) -> + NameOnly = string:substr(FlN, 1, string:rchr(FlN, $.)-1), + case file:read_file(FlN) of + {ok, BCont} -> + {ok, StrList} = load_lang_file(FlN, current_lang_code()), + {ok, Cont_0} = read_wscr_content(BCont, StrList), + Cont = wscr_map(Cont_0), + ScriptMode = script_mode(Cont), + + case lists:member(Op, ScriptMode) of + false -> unused; + true -> + WSCRType_0 = map_find(type, Cont, fun () -> detect end), + WSCRName = map_find(name, Cont, fun () -> "" end), + WSCRDesc = map_find(description, Cont, fun () -> "" end), + case WSCRType_0 of + detect -> + {WSCRType, ScriptFound} = case file_of_this_type(NameOnly) of + [FoundType | _] -> + {FoundType, + NameOnly ++ "." ++ FoundType}; + [] -> + {none, + none} + end; + _ -> + WSCRType = WSCRType_0, + ScriptFound_0 = NameOnly ++ "." ++ WSCRType_0, + ScriptFound = case file:read_file_info(ScriptFound_0) of + {ok, _} -> ScriptFound_0; + _ -> none + end + end, + {ok, {ScriptFound, WSCRType, WSCRName, WSCRDesc}} + end; + _ -> + error + end. + +script_mode(Cont) -> + case maps:find(mode, Cont) of + error -> [false]; + {ok, ModeStr} when is_list(ModeStr) -> + case string:trim(string:lowercase(ModeStr)) of + "import" -> [import]; + "export" -> [export]; + _ -> + script_mode_list(ModeStr) + end + end. + +script_mode_list("simple_body_command") -> + [body]; +script_mode_list(ModeList) -> + List1 = [list_to_atom(S) || S <- string:tokens(ModeList," ,")], + List2 = lists:filter(fun (B) -> lists:member(B, [vertex, edge, face, body, light]) end, List1), + if + List2 =:= [] -> [false]; + true -> List2 + end. + +file_of_this_type(NameOnly) -> + AllEng = scripting_engines:all_engines(), + lists:concat( + lists:map(file_of_this_type_1(NameOnly), + [Eng || {Eng,_} <- AllEng])). + +file_of_this_type_1(NameOnly) -> + fun(T) -> + ScriptFound_0 = NameOnly ++ "." ++ T, + case file:read_file_info(ScriptFound_0) of + {ok, _} -> [T]; + _ -> [] + end + end. + + +wscr_map(L) -> wscr_map(L, []). +wscr_map([], O) -> maps:from_list(O); +wscr_map([[A1, A2] | R], O) when is_list(A1), is_list(A2) -> + case lists:member(A1, [ + "mode", + "type", + "name", + "description", + "extensions", + "command_changes", + "command_inputs", + "params_title", + "params_preview", + "params", + "params_templates" + ]) of + true -> + wscr_map(R, [{list_to_atom(A1), A2}|O]); + false -> + wscr_map(R, [{A1, A2}|O]) + end; +wscr_map([_ | R], O) -> + wscr_map(R, O). + + +-define(CR_ONLY(C), (C =:= 10 orelse C =:= 13)). +-define(SPACE_ONLY(C), (C =:= 32 orelse C =:= 9)). +-define(SPACE_CR(C), (?CR_ONLY(C) orelse ?SPACE_ONLY(C))). + +read_wscr_until_eol(<>) when C =:= $\n -> + R; +read_wscr_until_eol(<<_, R/binary>>) -> + read_wscr_until_eol(R). + +read_wscr_content(R, StrList) -> + {Cont, []} = read_wscr_content(R, StrList, [], [], []), + {ok, Cont}. +read_wscr_content(<<>>, _StrList, [], [], Lines) -> + { lists:reverse(Lines), [] }; +read_wscr_content(<<>>, StrList, [], CLine, Lines) -> + read_wscr_content(<<>>, StrList, [], [], [lists:reverse(CLine) | Lines]); +read_wscr_content(<<>>, StrList, CWord, CLine, Lines) -> + read_wscr_content(<<>>, StrList, [], [lists:reverse(CWord) | CLine], Lines); + +read_wscr_content(<>, StrList, CWord, CLine, Lines) + when C =:= $# -> + R_1 = read_wscr_until_eol(R), + read_wscr_content(R_1, StrList, CWord, CLine, Lines); +%% String, not localized +read_wscr_content(<>=RInp, StrList, CWord, CLine, Lines) + when C =:= 34 -> + case CWord of + [] -> + {Str, R_1} = read_wscr_string(R), + read_wscr_content(R_1, StrList, CWord, [Str | CLine], Lines); + _ -> + read_wscr_content(RInp, StrList, [], [lists:reverse(CWord) | CLine], Lines) + end; +%% Quoted atom +read_wscr_content(<>=RInp, StrList, CWord, CLine, Lines) + when C =:= $' -> + case CWord of + [] -> + {Str, R_1} = read_wscr_atom(R), + read_wscr_content(R_1, StrList, CWord, [Str | CLine], Lines); + _ -> + read_wscr_content(RInp, StrList, [], [lists:reverse(CWord) | CLine], Lines) + end; +%% Syntax for term path +read_wscr_content(<<$%, $[, R/binary>>=RInp, StrList, CWord, CLine, Lines) -> + case CWord of + [] -> + {Str, R_1} = read_wscr_etp(R), + read_wscr_content(R_1, StrList, CWord, [Str | CLine], Lines); + _ -> + read_wscr_content(RInp, StrList, [], [lists:reverse(CWord) | CLine], Lines) + end; + + + +%% Advanced dialog structuring: +%% Example: +%% adv_params begin +%% {vframe,[ +%% ], [{title, ?__("Test"))}]} +%% end. +%% +read_wscr_content(<>, StrList, [], CLine, Lines) + when ?SPACE_CR(WS), ?CR_ONLY(CR) -> + {A, Bin_1} = parse_advparam(Bin, StrList), + read_wscr_content(Bin_1, StrList, [], [A|CLine], Lines); +read_wscr_content(<>=RInp, StrList, CWord, CLine, Lines) + when ?SPACE_CR(WS), ?CR_ONLY(CR) -> + read_wscr_content(RInp, StrList, [], [lists:reverse(CWord) | CLine], Lines); + +%% Multi-line string +%% Example: +%% """ +%% String contents +%% """ +%% +read_wscr_content(<>, StrList, [], CLine, Lines) + when DQ =:= 34 -> + {A, Bin_1} = advparam_mstr(Bin), + read_wscr_content(Bin_1, StrList, [], [A|CLine], Lines); +read_wscr_content(<>=RInp, StrList, CWord, CLine, Lines) + when DQ =:= 34 -> + read_wscr_content(RInp, StrList, [], [lists:reverse(CWord) | CLine], Lines); + + +%% String localized, the parser will use the string in StrList indicated by +%% the first argument integer, or the second argument is used as the string +%% if not found. +%% +read_wscr_content(<<$?, $_, $_, C, R/binary>>=RInp, StrList, CWord, CLine, Lines) + when C =:= 40 -> %% Opening parenthesis + case CWord of + [] -> + {LocStrID, Str, R_1} = read_wscr_string_locale(R), + case orddict:find(LocStrID, StrList) of + {ok, StrTranslated} -> + read_wscr_content(R_1, StrList, [], [StrTranslated | CLine], Lines); + _ -> + read_wscr_content(R_1, StrList, [], [Str | CLine], Lines) + end; + _ -> + read_wscr_content(RInp, StrList, [], [lists:reverse(CWord) | CLine], Lines) + end; +read_wscr_content(<>, StrList, CWord, CLine, Lines) + when ?SPACE_ONLY(C) -> + case CWord of + [] -> + read_wscr_content(R, StrList, [], CLine, Lines); + _ -> + read_wscr_content(R, StrList, [], [lists:reverse(CWord) | CLine], Lines) + end; +read_wscr_content(<>=RInp, StrList, CWord, CLine, Lines) + when C =:= ${ -> + case CWord of + [] -> + {SubList, R_2} = read_wscr_content(R, StrList, [], [], []), + read_wscr_content(R_2, StrList, [], [SubList | CLine], Lines); + _ -> + read_wscr_content(RInp, StrList, [], [lists:reverse(CWord) | CLine], Lines) + end; +read_wscr_content(<>=RInp, StrList, CWord, CLine, Lines) + when C =:= $} -> + case CWord of + [] -> + case CLine of + [] -> {lists:reverse(Lines), R}; + _ -> + read_wscr_content(RInp, StrList, [], + [], [lists:reverse(CLine) | Lines]) + end; + _ -> + read_wscr_content(RInp, StrList, [], + [lists:reverse(CWord) | CLine], Lines) + end; +read_wscr_content(<>, StrList, [], [], Lines) + when ?CR_ONLY(C) -> + read_wscr_content(R, StrList, [], + [], Lines); +read_wscr_content(<>, StrList, [], CLine, Lines) + when ?CR_ONLY(C) -> + read_wscr_content(R, StrList, [], + [], [lists:reverse(CLine) | Lines]); +read_wscr_content(<>=RInp, StrList, CWord, CLine, Lines) + when ?CR_ONLY(C) -> + read_wscr_content(RInp, StrList, [], + [lists:reverse(CWord) | CLine], Lines); +read_wscr_content(<>, StrList, CWord, CLine, Lines) -> + read_wscr_content(R, StrList, [C | CWord], CLine, Lines). + + +-spec backsl(integer()) -> integer(). +backsl($n) -> $\n; +backsl($r) -> $\r; +backsl($t) -> $\t; +backsl(C) when is_integer(C) -> C. + + +%% Reads the content of '...' in a .wscr +%% +-spec read_wscr_atom(binary()) -> {{atom,list()},binary()}. +read_wscr_atom(R_0) + when is_binary(R_0) -> + {Str, R} = read_wscr_atom(R_0, []), + {{atom,unicode:characters_to_list(iolist_to_binary(Str), utf8)}, R}. +read_wscr_atom(<<$\\, C, R/binary>>, Str) -> + read_wscr_atom(R, [backsl(C) | Str]); +read_wscr_atom(<>, Str) when C =:= $' -> + {lists:reverse(Str), R}; +read_wscr_atom(<>, Str) -> + read_wscr_atom(R, [C | Str]). + + +%% Reads the content of "..." in a .wscr +%% +-spec read_wscr_string(binary()) -> {list(),binary()}. +read_wscr_string(R_0) + when is_binary(R_0) -> + {Str, R} = read_wscr_string(R_0, []), + {unicode:characters_to_list(iolist_to_binary(Str), utf8), R}. +read_wscr_string(<<$\\, C, R/binary>>, Str) -> + read_wscr_string(R, [backsl(C) | Str]); +read_wscr_string(<>, Str) when C =:= 34 -> + {lists:reverse(Str), R}; +read_wscr_string(<>, Str) -> + read_wscr_string(R, [C | Str]). + +%% Reads the content of ?__(Digits,"...") in a .wscr +%% +-spec read_wscr_string_locale(binary()) -> + {integer(),string(),binary()} | {error, atom()}. +read_wscr_string_locale(R) + when is_binary(R) -> + read_wscr_string_locale(R, 1, [], []). +read_wscr_string_locale(<>, 1, Digits, Str) + when C >= $0, C =< $9 -> + read_wscr_string_locale(R, 1, [C | Digits], Str); +read_wscr_string_locale(<>, 2, Digits, _) + when DQ =:= 34-> + {Str, R_1} = advparam_mstr(Bin), + read_wscr_string_locale(R_1, 2, Digits, Str); +read_wscr_string_locale(<>, 2, Digits, _) + when C =:= 34 -> + {Str, R_1} = read_wscr_string(R), + read_wscr_string_locale(R_1, 2, Digits, Str); +read_wscr_string_locale(<>, N, Digits, Str) + when C =:= $, -> + read_wscr_string_locale(R, N+1, Digits, Str); +read_wscr_string_locale(<>, N, Digits, Str) + when ?SPACE_CR(C) -> + read_wscr_string_locale(R, N, Digits, Str); +read_wscr_string_locale(<>, 2, Digits, Str) + when C >= 41 -> %% Closing parenthesis + case string:to_integer(lists:reverse(Digits)) of + {error, _} -> LocStrID = 0; + {LocStrID, []} -> LocStrID + end, + {LocStrID, Str, R}; +read_wscr_string_locale(_, _, _, _) -> + {error, not_formatted_right}. + + +%% Reads the content of %[...] in a .wscr +%% +-spec read_wscr_etp(binary()) -> {{etp,string()},binary()}. +read_wscr_etp(R) + when is_binary(R) -> + read_wscr_etp(R, [], []). +read_wscr_etp(<>, Str, Context) when C =:= $[ -> + read_wscr_etp(R, [C | Str], [$[|Context]); +read_wscr_etp(<<$], R/binary>>, Str, []) -> + {{etp, unicode:characters_to_list(iolist_to_binary(lists:reverse(Str)), utf8)}, R}; +read_wscr_etp(<>, Str, [$[|Context]) when C =:= $] -> + read_wscr_etp(R, [C | Str], Context); +read_wscr_etp(<<$", R_0/binary>>, Str, Context) -> + {Str1, R} = read_wscr_string(R_0, []), + read_wscr_etp(R, [$",Str1,$" | Str], Context); +read_wscr_etp(<<$', R_0/binary>>, Str, Context) -> + {Str1, R} = read_wscr_atom(R_0, []), + read_wscr_etp(R, [$',Str1,$' | Str], Context); +read_wscr_etp(<>, Str, Context) -> + read_wscr_etp(R, [C | Str], Context). + + + +parse_advparam(A,StrList) + when is_binary(A), is_list(StrList) -> + erlang:put({?MODULE,expr_idx}, 0), + Ret = parse_advparam(A, StrList, []), + erlang:erase({?MODULE,expr_idx}), + Ret. +parse_advparam(<>, StrList, OL) + when ?CR_ONLY(CR), ?SPACE_CR(WS) -> + Str_0 = string:trim(lists:reverse(OL)), + {Exprs, Str} = advparam_bp1(Str_0), + {ok, Toks_0, _} = erl_scan:string(Str ++ "."), + Toks = advparam_bp2(Toks_0, StrList), + {ok, Term} = erl_parse:parse_term(Toks), + {{adv_term, Term, Exprs}, Bin}; +parse_advparam(<>, StrList, OL) -> + parse_advparam(Bin, StrList, [C|OL]). + +%% Substitute "%[]" with query expressions +%% +advparam_bp1(Str) + when is_list(Str) -> + advparam_bp1(Str, [], []). +advparam_bp1([$%,$[|Str], OL, OL2) -> + {IStr_0, Str_1} = advparam_bp1_1(Str), + {Exprs, IStr} = advparam_make_expr(IStr_0), + advparam_bp1(Str_1, lists:reverse(IStr) ++ OL, [Exprs|OL2]); +advparam_bp1([DQ|Str], OL, OL2) + when DQ =:= 34 -> + {InsideRev, Str_1} = advparam_bp1_str(Str), + advparam_bp1(Str_1, [DQ] ++ InsideRev ++ [DQ|OL], OL2); +advparam_bp1([C|Str], OL, OL2) -> + advparam_bp1(Str, [C|OL], OL2); +advparam_bp1([], OL, OL2) -> + {lists:reverse(OL2),lists:reverse(OL)}. + +advparam_bp1_1(Str) + when is_list(Str) -> + advparam_bp1_1(Str, []). +advparam_bp1_1([DQ|Str], OL) + when DQ =:= 34 -> + {InsideRev, Str_1} = advparam_bp1_str(Str), + advparam_bp1_1(Str_1, [DQ] ++ InsideRev ++ [DQ|OL]); +advparam_bp1_1([$[|Str], OL) -> + {Inside, Str_1} = advparam_bp1_1(Str), + advparam_bp1_1(Str_1, [$]] ++ Inside ++ [$[|OL]); +advparam_bp1_1([$]|Str], OL) -> + {lists:reverse(OL), Str}; +advparam_bp1_1([C|Str], OL) -> + advparam_bp1_1(Str, [C|OL]); +advparam_bp1_1([], OL) -> + {lists:reverse(OL), []}. + +advparam_bp1_str(Str) + when is_list(Str) -> + advparam_bp1_str(Str, []). +advparam_bp1_str([$\\,DQ|Str], OL) + when DQ =:= 34 -> + advparam_bp1_str(Str, [DQ,$\\|OL]); +advparam_bp1_str([DQ|Str], OL) + when DQ =:= 34 -> + {OL, Str}; +advparam_bp1_str([C|Str], OL) -> + advparam_bp1_str(Str, [C|OL]). + + +%% Substitute locale strings +%% +advparam_bp2(Toks_0, StrList) when is_list(Toks_0), is_list(StrList) -> + advparam_bp2(Toks_0, StrList, []). +advparam_bp2([{'?',_},{var,_,'__'},{'(',_},{integer,_,LocStrID}, + {',',A}|Toks_0], StrList, OL) -> + {StrConcat, Toks_1} = advparam_bp2_1(Toks_0), + Str = case orddict:find(LocStrID, StrList) of + {ok, StrTranslated} -> + StrTranslated; + _ -> + StrConcat + end, + T = {string,A,Str}, + advparam_bp2(Toks_1, StrList, [T|OL]); +advparam_bp2([T|Toks_0], StrList, OL) -> + advparam_bp2(Toks_0, StrList, [T|OL]); +advparam_bp2([], _StrList, OL) -> + lists:reverse(OL). + +advparam_bp2_1(Toks) when is_list(Toks) -> + advparam_bp2_1(Toks, []). +advparam_bp2_1([{string,_,Str}|Toks], OL) -> + advparam_bp2_1(Toks, [Str|OL]); +advparam_bp2_1([{')',_}|Toks], OL) -> + {lists:append(lists:reverse(OL)), Toks}. + + +advparam_mstr(A) when is_binary(A) -> + advparam_mstr(A, []). +advparam_mstr(<>, OL) + when ?CR_ONLY(CR), DQ =:= 34 -> + Str = string:trim(lists:reverse(OL)), + {Str, Bin}; +advparam_mstr(<>, OL) -> + advparam_mstr(Bin, [C|OL]). + +advparam_make_expr(Expr) -> + Num = erlang:get({?MODULE,expr_idx}), + erlang:put({?MODULE,expr_idx},Num+1), + Ident = "%%" ++ integer_to_list(Num), + {{list_to_atom(Ident), Expr}, "'" ++ Ident ++ "'"}. + + +%% Get the lang code. +current_lang_code() -> + atom_to_list(erlang:get(wings_lang)). + +%% Load a language file for the script wscr, they are in the same +%% format as a wings plugin's lang file. +-spec load_lang_file(file:name_all(), string()) -> {ok, list()} | error. +load_lang_file(WSCRFile, [_,_|_]=LangCode) when is_list(WSCRFile) -> + load_lang_file(WSCRFile, LangCode, wscr). +load_lang_file(WSCRFile, LangCode, Which) -> + LangFile = filename:rootname(WSCRFile) ++ "_" ++ LangCode ++ ".lang", + case file:consult(LangFile) of + {ok, [{_, LangSectionList}]} -> + case orddict:find(Which, LangSectionList) of + {ok, StrList} when is_list(StrList) -> {ok, StrList}; + error -> {ok, []} + end; + {error, _} -> + {ok, []} + end. + + +read_wscr_includes(Cont_0, WSCRAndStrLst) -> + case Cont_0 of + {ok, Cont} -> + {ok, Cont_1} = read_wscr_includes(Cont, WSCRAndStrLst, []), + {ok, Cont_1} + end. +read_wscr_includes([], _, O) -> + {ok, lists:reverse(O)}; +read_wscr_includes([["include", InclFile_0] | Cont], {WSCRFile, StrList}=WSCRAndStrLst, O) when is_list(WSCRFile) -> + InclFile = read_wscr_includes_shorthands(InclFile_0, filename:basename(WSCRFile)), + InclFile_1 = filename:absname(InclFile, filename:dirname(WSCRFile)), + case file:read_file(InclFile_1) of + {ok, BCont} -> + {ok, Cont_0} = read_wscr_includes(read_wscr_content(BCont, StrList), {InclFile_1, StrList}), + read_wscr_includes(Cont, WSCRAndStrLst, lists:reverse(Cont_0) ++ O); + _ -> + {error, file_not_included} + end; +read_wscr_includes([C | Cont], WSCRAndStrLst, OCont) -> + read_wscr_includes(Cont, WSCRAndStrLst, [C | OCont]). + +%% Certain short hands for an included wscr file +read_wscr_includes_shorthands(InclFile_0, WSCRFile) -> + WSCRFile_R = filename:rootname(WSCRFile), + lists:flatten(string:replace(InclFile_0, "(%)", WSCRFile_R)). + + +%% +%% wx UI +%% + +dlg_button_bar({Frameparts, Frameparts_sizer}, F, OkayLabelStr, CancelLabelStr) -> + Buttonbar = wxPanel:new(Frameparts), + Buttonbar_sizer = wxBoxSizer:new(?wxHORIZONTAL), + wxPanel:setSizer(Buttonbar, Buttonbar_sizer), + Btnokay = wxButton:new(Buttonbar, ?wxID_ANY, [ {label, OkayLabelStr} ]), + wxWindow:connect(Btnokay, command_button_clicked, [{callback, fun(#wx{event=#wxCommand{commandInt=_Int}}, _Obj) -> + wxDialog:endModal(F, ?wxID_OK) + end}]), + wxBoxSizer:add(Buttonbar_sizer, Btnokay), + BtnCancel = wxButton:new(Buttonbar, ?wxID_ANY, [ {label, CancelLabelStr} ]), + wxWindow:connect(BtnCancel, command_button_clicked, [{callback, fun(#wx{event=#wxCommand{commandInt=_Int}}, _Obj) -> + wxDialog:endModal(F, ?wxID_CANCEL) + end}]), + wxBoxSizer:add(Buttonbar_sizer, BtnCancel), + wxBoxSizer:add(Frameparts_sizer, Buttonbar, [ {flag, ?wxALL}, {border, 3}]), + {Btnokay, BtnCancel}. + + + + +%%% +%%% Script Preference Dialog +%%% + +dlg_script_preference(#st{}=St) -> + Pag_Shapes = + {?__(3,"Shapes"), {vframe, dlg_script_preference_paths(setting_paths_shapes)}}, + Pag_Commands = + {?__(4,"Commands"), {vframe, dlg_script_preference_paths(setting_paths_commands)}}, + Pag_Imp_Export = + {?__(5,"Import/Export"), {vframe, dlg_script_preference_paths(setting_paths_import_export)}}, + Pag_Interpreters = + {?__(6,"Interpreters"), {vframe, dlg_script_preference_paths(interpreters)}}, + Pag_Debug = + {?__(7,"Debug"),{vframe, dlg_script_preference_paths(setting_show_tuple)}}, + + Dialog = + [ + {?__(2,"Enable Scripts"), get_pref(setting_enable), [{key,setting_enable}]}, + {vframe, [ + {oframe, [ + Pag_Shapes, + Pag_Commands, + Pag_Imp_Export, + Pag_Interpreters, + Pag_Debug], 1, [{style, buttons}]} + ], [{key,pnl_script_pref}, {show,true}, {margin,false}]} + + ], + wpa:dialog(?__(1,"Script Preference"), Dialog, fun(Attr) -> dlg_script_result(Attr, St) end). + +dlg_script_result(Attr0, #st{}=St) -> + Defaults = get_user_prefs(get_defaults()), + Attr1 = pref_path_result([ + setting_paths_shapes, + setting_paths_commands, + setting_paths_import_export], Attr0), + Attr = [{Key,proplists:get_value(Key,Attr1,DVal)} || {Key,DVal} <- Defaults], + set_user_prefs(Attr), + St. + + + +dlg_script_pref_eng(_, no_bin) -> []; +dlg_script_pref_eng(Eng, HasArgs) + when is_boolean(HasArgs) -> + Atom = list_to_atom("setting_" ++ Eng ++ "_int_path"), + dlg_script_preference_path_item({int_path, Atom, Eng}, get_pref(Atom)) + ++ dlg_script_pref_eng_1(Eng, HasArgs). + +dlg_script_pref_eng_1(_, false) -> []; +dlg_script_pref_eng_1(Eng, true) -> + Atom = list_to_atom("setting_" ++ Eng ++ "_arguments"), + dlg_script_preference_path_item({arguments, Atom, Eng}, get_pref(Atom)). + + +dlg_script_preference_paths(setting_show_tuple=Key) -> + ChkEnableShowTuple = get_pref(Key), + [{?__(4,"Enable Debug Return Data"), ChkEnableShowTuple, [{key,Key}]}]; +dlg_script_preference_paths(interpreters) -> + AllEng = scripting_engines:all_engines(), + lists:append( + [ dlg_script_pref_eng(Eng, HasArgs) + || {Eng, HasArgs} <- AllEng]); +dlg_script_preference_paths(Key) -> + Paths = get_pref(Key), + [{label, ?__(9,"Each script must have with it a file with the extension " + ".wscr that contains its name, description and parameters.")}, + panel] ++ + dlg_script_preference_path_item(Key, Paths). + +dlg_script_preference_path_item(setting_paths_shapes=Key, Paths) -> + [{label, ?__(1,"Paths for Shape Scripts")}, + pref_path_qs(Paths, Key, table_labels()) + ]; +dlg_script_preference_path_item(setting_paths_commands=Key, Paths) -> + ChkEnableScriptsCommands = get_pref(setting_enable_commands), + [{label, ?__(2,"Paths for Commands Scripts")}, + pref_path_qs(Paths, Key, table_labels()), + {?__(3,"Enable Scripts for Commands Menu"), ChkEnableScriptsCommands, + [{key,setting_enable_commands}]} + ]; +dlg_script_preference_path_item(setting_paths_import_export=Key, Paths) -> + ChkEnableScriptsImport = get_pref(setting_enable_import), + ChkEnableScriptsExport = get_pref(setting_enable_export), + [{label, ?__(4,"Paths for Import/Export Scripts")}, + pref_path_qs(Paths, Key, table_labels()), + {?__(5,"Enable Scripts for Import Menu"), ChkEnableScriptsImport, + [{key,setting_enable_import}]}, + {?__(6,"Enable Scripts for Export Menu"), ChkEnableScriptsExport, + [{key,setting_enable_export}]} + ]; +dlg_script_preference_path_item({int_path, Key, EngName}, Path) -> + [{label_column, [ + {string:replace(?__(8,"% Interpreter Path"), "%", EngName), + {button, {text, Path, [{key,Key}, {width,30}, wings_job:browse_props()]}}} + ]}]; +dlg_script_preference_path_item({arguments, Key, EngName}, Path) -> + [{label_column, [ + {string:replace(?__(9,"% Extra Interpreter Arguments"), "%", EngName), + {text, Path, [{key,Key}, {width,30}]}} + ]}]. + +table_labels() -> + {?__(1,"Paths"), + ?__(2,"Add"), + ?__(3,"Edit"), + ?__(4,"Del")}. + + +get_defaults() -> + AllEng = scripting_engines:all_engines(), + Defaults = [ + {setting_enable, true}, + {setting_enable_commands, false}, + {setting_enable_import, false}, + {setting_enable_export, false}, + {setting_paths_shapes, ""}, + {setting_paths_commands, ""}, + {setting_paths_import_export, ""}, + {setting_show_tuple, false} + ], + get_defaults_conf(Defaults ++ + lists:append( + [get_defaults_eng(Eng, HasArgs) + || {Eng, HasArgs} <- AllEng])). + + +%% +%% A defaults.conf file can specify some default settings +%% +get_defaults_conf(List0) -> + Dir = filename:absname(code:where_is_file(init_dir())), + case file:consult(filename:join(Dir, "defaults.conf")) of + {ok, List} -> + orddict:merge( + fun (_,V1,_) -> V1 end, + orddict:from_list(List), + orddict:from_list(List0)); + _ -> + List0 + end. + + +get_defaults_eng(_, no_bin) -> []; +get_defaults_eng(Eng, HasArgs) + when is_boolean(HasArgs) -> + [{list_to_atom("setting_" ++ Eng ++ "_int_path"), ""}] ++ + get_defaults_eng_1(Eng, HasArgs). + +get_defaults_eng_1(_, false) -> []; +get_defaults_eng_1(Eng, true) -> + [{list_to_atom("setting_" ++ Eng ++ "_arguments"), ""}]. + + + +set_pref_defaults() -> + [wpa:pref_set_default(?MODULE,Key,Val) || {Key,Val} <- get_defaults()]. + +get_pref(Key) -> + wpa:pref_get(?MODULE, Key). + +set_user_prefs(Attr) -> + wpa:pref_set(?MODULE, Attr). + +get_user_prefs(KeyDefs) when is_list(KeyDefs) -> + [{Key, wpa:pref_get(?MODULE, Key, Def)} || {Key, Def} <- KeyDefs]. + + +%%% +%%% + +%% +%% Preference path qs for Scripting + +pref_path_qs(Paths, Key, {LabelTab, LabelAdd, LabelEdit, LabelDel}) -> + List0 = [string:trim(A) || A <- string:split(Paths,"\n",all)], + List1 = [A || A <- List0, length(A) > 0], + List2 = [{{A,A}} || A <- List1], + {vframe, [ + {table, [{LabelTab}|List2], + [{key,{Key,table}},{max_rows,10},{col_widths,{50}}, + {sel_style,single},{hook,fun pref_path_dlg_script_hook/3}]}, + {hframe, [ + %{button, + {text, "", [{key,{Key,text}}, {width,30}]},%}, {dialog_type,dir_dialog} + {button, LabelAdd, add, [{key,{Key,add}},{hook,fun pref_path_dlg_script_hook/3}]}, + {button, LabelEdit, edit, [{key,{Key,edit}},{hook,fun pref_path_dlg_script_hook/3}]}, + {button, LabelDel, del, [{key,{Key,del}},{hook,fun pref_path_dlg_script_hook/3}]} + ]} + ]}. +pref_path_dlg_script_hook(Key, _Val, Sto) -> + case Key of + {Tab,add} -> + NewPath = wings_dialog:get_value({Tab,text},Sto), + case wings_dialog:get_value({Tab,table},Sto) of + {_, List} when is_list(List), length(NewPath) > 0 -> + Val1 = {[], List ++ [{{NewPath, NewPath}}]}, + wings_dialog:set_value({Tab,table},Val1,Sto), + wings_dialog:set_value({Tab,text},"",Sto); + _ -> + ok + end; + {Tab,edit} -> + NewPath = wings_dialog:get_value({Tab,text},Sto), + case wings_dialog:get_value({Tab,table},Sto) of + {[Sel], List} when is_integer(Sel), length(List) > Sel, length(NewPath) > 0 -> + case lists:split(Sel,List) of + {List1_1, [_|List2_1]} -> + Val1 = {[Sel], List1_1++[{{NewPath, NewPath}}|List2_1]}, + wings_dialog:set_value({Tab,table},Val1,Sto); + _ -> + ok + end; + {_, List} when is_list(List), length(NewPath) > 0 -> + Val1 = {[], List ++ [{{NewPath, NewPath}}]}, + wings_dialog:set_value({Tab,table},Val1,Sto), + wings_dialog:set_value({Tab,text},"",Sto); + _ -> + ok + end; + {Tab,del} -> + case wings_dialog:get_value({Tab,table},Sto) of + {[Sel], List} when is_integer(Sel), length(List) > Sel -> + case lists:split(Sel,List) of + {List1_1, [_|List2_1]} -> + Val1 = {[Sel], List1_1++List2_1}, + wings_dialog:set_value({Tab,table},Val1,Sto); + _ -> + ok + end; + _ -> + ok + end; + {Tab,table} -> + case wings_dialog:get_value({Tab,table},Sto) of + {[Sel], List} when is_integer(Sel), length(List) > Sel -> + {{_, Path}} = lists:nth(Sel+1, List), + wings_dialog:set_value({Tab,text},Path,Sto), + ok; + _ -> + ok + end + end, + ok. + +pref_path_result(PathConfigKeys, Attr0) when is_list(PathConfigKeys) -> + EntPaths = maps:from_list([{A,proplists:get_value({A,text}, Attr0, "")} || A <- PathConfigKeys]), + Set = sets:from_list(PathConfigKeys), + F = fun ({{Key,table},Val0}=Tuple) -> + case sets:is_element(Key, Set) of + true -> + {_Sel,Val} = Val0, + EntPath = maps:get(Key, EntPaths), + List1 = lists:append([[B || B <- string:split(P,";",all)] || {{_,P}} <- Val++[{{"",EntPath}}]]), + {Key, string:join(pref_path_clean_path_list([string:trim(P) || P <- List1]),"\n")}; + _ -> + Tuple + end; + ({{_,text},_}) -> false; + (Tuple) -> Tuple + end, + [A || A <- lists:map(F, Attr0), A =/= false]. + + +%% Clean path list of empty strings and duplicates. +%% +pref_path_clean_path_list(List) when is_list(List) -> + pref_path_clean_path_list(List,sets:new(),[]). +pref_path_clean_path_list([],_,OL) -> + lists:reverse(OL); +pref_path_clean_path_list([""|List],Seen,OL) -> + pref_path_clean_path_list(List,Seen,OL); +pref_path_clean_path_list([Path|List],Seen,OL) -> + case sets:is_element(Path,Seen) of + true -> + pref_path_clean_path_list(List,Seen,OL); + _ -> + pref_path_clean_path_list(List,sets:add_element(Path,Seen),[Path|OL]) + end. + +%%% +%%% + + +%% +%% Prepare mainly list data structures for write_scm +%% + +prepare_parameter_list_for_scm(List) when is_list(List) -> + lists:map(fun ({K, A1}) when is_atom(K) -> + [A1_1] = prepare_parameter_list_for_scm([A1]), + [{atom, list_to_binary(atom_to_list(K))}, A1_1]; + (A1) when is_binary(A1) -> {string, A1}; + (Str) when is_list(Str) -> {string, Str}; + (A1) -> A1 + end, List). +prepare_more_parameters(List) -> + lists:map(fun + ([{string, A1}, A2]) -> + [ {string, A1}, prepare_more_parameters_r(A2) ]; + + ([A1, A2]) -> + [ {string, A1}, prepare_more_parameters_r(A2) ] + end, List). +prepare_more_parameters_r({string, A1}) when is_binary(A1) -> {string, A1}; +prepare_more_parameters_r({atom, A1}) when is_binary(A1) -> {atom, A1}; +prepare_more_parameters_r(A1) when is_binary(A1) -> {string, A1}; +prepare_more_parameters_r(Tuple) + when is_tuple(Tuple), is_atom(element(1, Tuple)), + element(1, Tuple) =/= atom, + element(1, Tuple) =/= string -> + [FrontAtom | List2] = tuple_to_list(Tuple), + [ {atom, binstr(atom_to_list(FrontAtom))} | + lists:map(fun(A1) -> prepare_more_parameters_r(A1) end, List2)]; +prepare_more_parameters_r(List) when is_list(List) -> + lists:map(fun(A1) -> prepare_more_parameters_r(A1) end, List); +prepare_more_parameters_r(A1) when is_atom(A1) -> {atom, binstr(atom_to_list(A1))}; +prepare_more_parameters_r(A1) -> A1. + + +prepare_string_pairs(StrList) when is_list(StrList) -> + [prepare_string_pairs_1(P) || P <- StrList]. +prepare_string_pairs_1({Int, Str}) + when is_list(Str) -> + {Int, {string, binstr(Str)}}; +prepare_string_pairs_1(A) -> A. + + +%% +%% Symbolic expression writer +%% +%% Values sent to the running script are always sent in a list +%% so the expression parser always can expect all communication +%% to be between opening and closing parenthesis. +%% + +write_scm(A) when is_list(A) -> + ?DEBUG_FMT("write_scm -> ~p~n~n", [A]), + write_scm(A, [<<"(">>], false). +write_scm([], BList, _) -> + lists:reverse([<<")">>|BList]); +write_scm(AList, BList, true) -> + write_scm(AList, [<<" ">> | BList], false); +write_scm([{atom, A} | AList], BList, false) -> + write_scm(AList, [A | BList], true); +write_scm([{string, {string, A}} | AList], BList, false) -> + write_scm([{string, A} | AList], BList, false); +write_scm([{string, A} | AList], BList, false) -> + A_1 = string:replace(A, "\\", "\\\\", all), + A_2 = binstr(string:replace(A_1, "\"", "\\\"", all)), + write_scm(AList, [<<"\"", A_2/binary, "\"">> | BList], true); +write_scm([{X, Y} | AList], BList, false) + when is_number(X), is_number(Y) -> %% coordinate + TS = iolist_to_binary(io_lib:format("#(~w ~w)", [X, Y])), + write_scm(AList, [TS | BList], true); +write_scm([{X, Y} | AList], BList, false) + when is_list(X), is_list(Y); + is_tuple(X), is_tuple(Y); + is_number(X), is_atom(Y); + is_number(X), is_tuple(Y); %% orddict element + is_number(X), is_list(Y) -> %% orddict element + [_ | X_1] = lists:reverse(write_scm([X], [], false)), + [_ | Y_1] = lists:reverse(write_scm([Y], [], false)), + Args = [<<")">> | Y_1] ++ [<<" ">> | X_1], + write_scm(AList, [lists:reverse(Args), <<"#(">> | BList], true); +write_scm([{X, Y, Z} | AList], BList, false) + when is_number(X), is_number(Y), is_number(Z) -> + TS = iolist_to_binary(io_lib:format("#(~w ~w ~w)", [X, Y, Z])), + write_scm(AList, [TS | BList], true); +write_scm([{X, Y, Z} | AList], BList, false) + when is_list(X), is_list(Y), is_list(Z); + is_tuple(X), is_tuple(Y), is_tuple(Z) -> + [_ | X_1] = lists:reverse(write_scm([X], [], false)), + [_ | Y_1] = lists:reverse(write_scm([Y], [], false)), + [_ | Z_1] = lists:reverse(write_scm([Z], [], false)), + Args = [<<")">> | Z_1] ++ [<<" ">> | Y_1] ++ [<<" ">> | X_1], + write_scm(AList, [lists:reverse(Args), <<"#(">> | BList], true); +write_scm([BigTuple | AList], BList, false) when is_tuple(BigTuple) -> + A_1 = write_scm_tuple(tuple_to_list(BigTuple)), + write_scm(AList, [A_1 | BList], true); +write_scm([A | AList], BList, false) when is_list(A) -> + A_1 = write_scm(A), + write_scm(AList, [A_1 | BList], true); +write_scm([true | AList], BList, false) -> + write_scm(AList, [<<"#t">> | BList], true); +write_scm([false | AList], BList, false) -> + write_scm(AList, [<<"#f">> | BList], true); +write_scm([A | AList], BList, false) + when is_atom(A) -> + write_scm(AList, [io_lib:format("~w", [A]) | BList], true); +write_scm([A | AList], BList, false) when is_number(A) -> + write_scm(AList, [io_lib:format("~w", [A]) | BList], true). + +write_scm_tuple(A) when is_list(A) -> + write_scm(A, [<<"#(">>], false). + + +%% +%% Symbolic expression reader for script input/output +%% + +scm_parse(A) when is_binary(A) -> + scm_parse(A, [], []). +scm_parse(<<>>, List, []) -> {lists:reverse(List), <<>>}; +scm_parse(<<>>, List, Tok) when length(Tok) > 0 -> + scm_parse(<<>>, [bare_word(Tok) | List], []); +scm_parse(<<34, R/binary>>, List, Tok) -> + {Str, R_0} = scm_parse_str(R), + scm_parse(R_0, [{string, Str} | List], Tok); +scm_parse(<<$#, C, R/binary>>=RInp, List, Tok) when C =:= $( -> + case Tok of + [] -> + {SubList, R_0} = scm_parse(R, [], []), + scm_parse(R_0, [list_to_tuple(SubList) | List], []); + _ when length(Tok) > 0 -> + scm_parse(RInp, [bare_word(Tok) | List], []) + end; +scm_parse(<<$', C, R/binary>>=RInp, List, Tok) when C =:= $( -> + case Tok of + [] -> + {SubList, R_0} = scm_parse(R, [], []), + scm_parse(R_0, [SubList | List], []); + _ when length(Tok) > 0 -> + scm_parse(RInp, [bare_word(Tok) | List], []) + end; +scm_parse(<>=RInp, List, Tok) when C =:= $( -> + case Tok of + [] -> + {SubList, R_0} = scm_parse(R, [], []), + scm_parse(R_0, [SubList | List], []); + _ when length(Tok) > 0 -> + scm_parse(RInp, [bare_word(Tok) | List], []) + end; +scm_parse(<>=RInp, List, Tok) when C =:= $) -> + case Tok of + [] -> {lists:reverse(List), R}; + _ when length(Tok) > 0 -> + scm_parse(RInp, [bare_word(Tok) | List], []) + end; +scm_parse(<>=RInp, List, Tok) + when ?SPACE_CR(C) -> + case Tok of + [] -> scm_parse(R, List, []); + _ -> scm_parse(RInp, [bare_word(Tok) | List], []) + end; +scm_parse(<>=Num_S, List, []) + when C >= $0, C =< $9; C =:= $.; C =:= $- -> + {Num, RInp} = scm_parse_num(Num_S), + scm_parse(RInp, [Num | List], []); +scm_parse(<>, List, Tok) -> + scm_parse(R, List, [C | Tok]). + + +scm_parse_num(A) when is_binary(A) -> + scm_parse_num(A, []). +scm_parse_num(<>=R, Num_S0) + when ?SPACE_ONLY(C); C =:= $) -> + Num_S = lists:reverse(Num_S0), + case string:to_float(Num_S) of + {error, _} -> + case string:to_integer(Num_S) of + {error, _} -> {<<"?">>, R}; + {Num_0, []} -> {Num_0, R} + end; + {Num_0, []} -> {Num_0, R} + end; +scm_parse_num(<>, Num_S) -> + scm_parse_num(R, [C | Num_S]). + +scm_parse_str(A) when is_binary(A) -> + scm_parse_str(A, []). +scm_parse_str(<<$\\, C, R/binary>>, Str) -> + scm_parse_str(R, [backsl(C) | Str]); +scm_parse_str(<<34, R/binary>>, Str) -> + {iolist_to_binary(lists:reverse(Str)), R}; +scm_parse_str(<>, Str) -> + scm_parse_str(R, [C | Str]). + +bare_word(A_0) -> + A_1 = lists:reverse(A_0), + case A_1 of + "#t" -> true; + "#f" -> false; + [C|_] when C =:= $% -> + {atom, iolist_to_binary(A_1)}; + _ -> list_to_atom(A_1) + end. + +get_wscr_param_number(Num_S) -> + case string:to_float(Num_S) of + {error, _} -> + case string:to_integer(Num_S) of + {error, _} -> Num_S; + {Num_0, []} -> Num_0 + end; + {Num_0, []} -> Num_0 + end. + +read_wscr_file_content(WSCRFile) -> + case file:read_file(WSCRFile) of + {ok, BCont} -> + {ok, StrList} = load_lang_file(WSCRFile, current_lang_code()), + {ok, Cont_0} = + read_wscr_includes(read_wscr_content(BCont, StrList), + {WSCRFile, StrList}), + {ok, Cont_0}; + _ -> + error + end. + +get_wscr_params_default_param_title() -> + ?__(1,"Set Parameters"). + + +get_template_opts(A, B) + when is_atom(A), is_list(B) -> + get_template_opts(A, B, []). +get_template_opts(A, ["include_"++_=Opt|Opts], OL) + when A =:= import; + A =:= export -> + Opt_1 = case Opt of + "include_normals" -> include_normals; + "include_uvs" -> include_uvs; + "include_colors" -> include_colors; + _ -> false + end, + if + Opt_1 =:= false -> + get_template_opts(A, Opts, OL); + true -> + get_template_opts(A, Opts, [Opt_1|OL]) + end; +get_template_opts(A, [_|Opts], OL) -> + get_template_opts(A, Opts, OL); +get_template_opts(_A, [], OL) -> + lists:reverse(OL). + +get_wscr_templates(Cont) when is_map(Cont) -> + ParamsTplsLists_0 = map_find(params_templates, Cont, fun () -> [] end), + lists:map(fun + (["template" | L]) -> + case L of + ["import", Opts | _] -> + {import, get_template_opts(import, string:tokens(Opts, " ,"))}; + ["import"] -> + {import, []}; + ["export", Opts | _] -> + {export, get_template_opts(export, string:tokens(Opts, " ,"))}; + ["export"] -> + {export, []}; + _ -> + {unknown, []} + end; + (_) -> + {unknown, []} + end, ParamsTplsLists_0). + +get_wscr_params(Cont_0) -> + Cont = wscr_map(Cont_0), + ParamsTitle = map_find(params_title, Cont, fun () -> get_wscr_params_default_param_title() end), + ParamsPreview = dlg_menu_item_bool(map_find(params_preview, Cont, fun () -> "0" end)), + ParamsLists_0 = map_find(params, Cont, fun () -> [] end), + ParamsTplsLists = get_wscr_templates(Cont), + ParamsLists = lists:map(get_wscr_params_1(), ParamsLists_0), + + {ok, ParamsTitle, ParamsPreview, ParamsLists, ParamsTplsLists}. + +map_find(Key, Cont, NoVal) when is_function(NoVal) -> + case maps:find(Key, Cont) of + error -> NoVal(); + {ok, Val} -> Val + end. + + +get_wscr_params_1() -> + fun + (["param", ParamName | List]) -> + get_wscr_params_1_opt_param(ParamName, List); + + (["adv_params", {adv_term,Dialog,Vals}]) -> + {adv_params,Dialog,Vals}; + + (["menu", Str | List]) -> + get_wscr_params_1_opt_menu(Str, List, menu); + (["vradio", Str | List]) -> + get_wscr_params_1_opt_menu(Str, List, vradio); + (["hradio", Str | List]) -> + get_wscr_params_1_opt_menu(Str, List, hradio); + + (["checkbox", Str | List]) -> + get_wscr_params_1_opt_3(Str, List, fun dlg_checkbox_param/3); + (["browse", Str | List]) -> + get_wscr_params_1_opt_3(Str, List, fun dlg_browse_param/3); + + (_) -> + {"???", 0} + end. + +get_wscr_params_1_opt_param(ParamName, [ParamDefault, ParamKey | _]) -> + {ParamName, get_wscr_param_number(ParamDefault), + [{key,list_to_atom(ParamKey)}]}; +get_wscr_params_1_opt_param(ParamName, [ParamDefault]) -> + {ParamName, get_wscr_param_number(ParamDefault)}. + + +get_wscr_params_1_opt_3(Str, [Default, Opts_S], F) -> + F(Str, Default, Opts_S); +get_wscr_params_1_opt_3(Str, [Default], F) -> + F(Str, Default, ""); +get_wscr_params_1_opt_3(Str, [], F) -> + F(Str, "", ""). + +get_wscr_params_1_opt_menu(Str, [Default_S, Opts_S, [[_|_]=_|_]=List], Atom) -> + dlg_menu_param(Atom, Str, Default_S, Opts_S, List); +get_wscr_params_1_opt_menu(Str, [Default_S, [[_|_]=_|_]=List], Atom) -> + dlg_menu_param(Atom, Str, Default_S, "", List); +get_wscr_params_1_opt_menu(Str, [[[_|_]=_|_]=List], Atom) -> + dlg_menu_param(Atom, Str, dlg_menu_param_first_item(List), "", List). + + +dlg_menu_param(Type, Str, Default_S, Opts_S, List) -> + Default = dlg_menu_item_val(Default_S), + L2 = [dlg_menu_item(A) || A <- List], + case string:tokens(Opts_S, ",") of + [] -> + {menu,{Type,L2},Str,Default}; + Opts1 when is_list(Opts1) -> + {menu,{Type,L2},Str,Default,dlg_menu_opts(Opts1)} + end. + +dlg_menu_param_first_item([["item",_,Val]=_|_]=_) -> + dlg_menu_item_val(Val). + +dlg_checkbox_param(Str, Default_S, Opts_S) -> + Default = dlg_menu_item_bool(Default_S), + case string:tokens(Opts_S, ",") of + [] -> + {checkbox,{checkbox},Str,Default}; + Opts1 when is_list(Opts1) -> + {checkbox,{checkbox},Str,Default,dlg_menu_opts(Opts1)} + end. + +dlg_browse_param(Str, Default, Opts_S) -> + case string:tokens(Opts_S, ",") of + [] -> + {browse,{browse},Str,Default}; + Opts1 when is_list(Opts1) -> + {browse,{browse},Str,Default,dlg_menu_opts(Opts1)} + end. + +dlg_menu_opts(Opts) -> + [begin + case string:split(O,"=") of + [Key] -> + {key,list_to_atom(Key)}; + [Key,Val] -> + case lists:member(Key, ["key", "dialog_type"]) of + true -> + {list_to_atom(Key),list_to_atom(Val)}; + _ -> %% "title" + {list_to_atom(Key),Val} + end + end + end || O <- Opts]. + +dlg_menu_item(["item", Str, Val]) -> + {Str, dlg_menu_item_val(Val)}; +dlg_menu_item(_) -> + false. + +dlg_menu_item_bool(Val) + when is_list(Val) -> + case string:lowercase(string:trim(Val)) of + "t" ++ _ -> true; + "f" ++ _ -> false; + "y" ++ _ -> true; + "n" ++ _ -> false; + "1" -> true; + "0" -> false; + _ -> Val + end; +dlg_menu_item_bool({atom,Val}) -> + dlg_menu_item_bool(Val). + +dlg_menu_item_val({atom,Val}) + when is_list(Val) -> + list_to_atom(Val); +dlg_menu_item_val(Val) + when is_list(Val) -> + case string:to_float(Val) of + {Num, []} -> Num; + _ -> dlg_menu_item_val_1(Val) + end. +dlg_menu_item_val_1(Val) + when is_list(Val) -> + case string:to_integer(Val) of + {Num, []} -> Num; + _ -> dlg_menu_item_val_2(Val) + end. +dlg_menu_item_val_2(Val) + when is_list(Val) -> + F = fun (C) when C >= $a, C =< $z; + C >= $A, C =< $Z; + C >= $0, C =< $9; + C =:= $_ -> true; + (_) -> false + end, + case lists:all(F, Val) of + true -> + list_to_atom(Val); + _ -> + Val + end. + + +get_wscr_import_export_params(Cont_0) -> + Cont = wscr_map(Cont_0), + ExtensionsLists = map_find(extensions, Cont, fun () -> [] end), + Templates = get_wscr_templates(Cont), + ExtList = lists:map(fun + (["ext", Extension, ExtensionDesc | _]) -> + {Extension, ExtensionDesc}; + (_) -> + {".txt", "???"} + end, ExtensionsLists), + {ok, ExtList, Templates}. + + +fill_extra_file_inputs(ExtraFileChoosers, #command_rec{extrafileinputs=ExtraFiles}=CommandRec, _St, F) + when length(ExtraFileChoosers) > length(ExtraFiles) -> + {FileVarName, ChooserPs} = lists:nth(length(ExtraFiles)+1, ExtraFileChoosers), + Title = proplists:get_value(title, ChooserPs, ?__(1,"Choose File")), + Exts = proplists:get_value(exts, ChooserPs, []), + case get_wscr_import_export_params([Exts]) of + {ok, Extensions, _} -> + Ps = [{extensions,Extensions},{title,Title}], + wpa:import_filename(Ps, fun(N) -> + F( CommandRec#command_rec{ + extrafileinputs=[{FileVarName, N} | CommandRec#command_rec.extrafileinputs] + } ) + end) + end; +fill_extra_file_inputs(ExtraFileChoosers, #command_rec{extrafileinputs=ExtraFiles}=_CommandRec, _St, _F) + when length(ExtraFileChoosers) =:= length(ExtraFiles) -> + {file_inputs, ExtraFiles}. + + +fill_extra_files(ExtraFileChoosers, #command_rec{extrafileinputs=ExtraFiles}=_CommandRec) + when length(ExtraFileChoosers) =:= length(ExtraFiles) -> + {file_inputs, ExtraFiles}. + +to_dialog_params(List, State, Dict) -> + to_dialog_params(List, State, Dict, []). +to_dialog_params([{A,Val,C} | List], State, Dict, OList) + when is_list(A) -> + to_dialog_params(List, State, Dict, + [{A, to_dialog_param_1(Val, State, Dict), C} + | OList]); +to_dialog_params([{A,Val} | List], State, Dict, OList) + when is_list(A) -> + to_dialog_params(List, State, Dict, + [{A, to_dialog_param_1(Val, State, Dict)} + | OList]); +to_dialog_params([{adv_params,Tuple,Vals} | List], State, Dict, OList) + when is_list(Vals) -> + to_dialog_params(List, State, Dict, + [{adv_params,Tuple,[{Label,to_dialog_param_1({etp,Val}, State, Dict)} || {Label,Val} <- Vals]} + | OList]); +to_dialog_params([{Atom,AtomSet,A,Val,C} | List], State, Dict, OList) + when is_list(A),is_atom(Atom),is_tuple(AtomSet) -> + to_dialog_params(List, State, Dict, + [{Atom,AtomSet,A, to_dialog_param_1(Val, State, Dict), C} + | OList]); +to_dialog_params([{Atom,AtomSet,A,Val} | List], State, Dict, OList) + when is_list(A),is_atom(Atom),is_tuple(AtomSet) -> + to_dialog_params(List, State, Dict, + [{Atom,AtomSet,A, to_dialog_param_1(Val, State, Dict)} + | OList]); +to_dialog_params([], _, _, OList) -> + lists:reverse(OList). + +to_dialog_param_1(Number, _State, _Dict) + when is_integer(Number); + is_float(Number); + is_atom(Number) -> + Number; +to_dialog_param_1(PV, State, Dict) -> + {Val, _} = crun_pv(PV, State, Dict), + Val. + + +append_extra_files(ExtraFiles, ParamsSetVars) -> + PSScriptParams = get_map(script_params, ParamsSetVars), + List1 = append_extra_files_1(ExtraFiles, PSScriptParams), + [[binstr(K), V] || {K, V} <- List1]. +append_extra_files_1(ExtraFiles, PSScriptParams) -> + [{K1,{string,V1}} || {K1,V1} <- ExtraFiles] ++ maps:to_list(PSScriptParams). + + +make_shape_askdialog( + Params, Title, false, SettableParams, State_1, Dict, Templates, + CommandRec, _St) -> + askdialog(Params, Title, + to_dialog_params(SettableParams, State_1, Dict), Templates, + fun(Res) -> {shape,{shape_from_script, {CommandRec, Res}}} end + ); +make_shape_askdialog( + Params, Title, true, SettableParams, State_1, Dict, Templates, + #command_rec{scrfile=ScriptFileName,scrtype=ScriptType}=CommandRec, St) -> + case run_script_w_preview(ScriptType, ScriptFileName, + get_settings_for_run_script()) + of {ok, FP} -> + askdialog_w_prev(Params, Title, + to_dialog_params(SettableParams, State_1, Dict), Templates, + fun(Res) -> {shape,{shape_from_script, {CommandRec, Res}}} end, St, FP + ) + end. + + +make_shape_from_script(Params, #command_rec{wscrcont=WSCRContent}=CommandRec, St) + when is_atom(Params) -> + Dict = orddict:from_list([{"st", St}]), + {_, State_1} = crun_section("params_init", WSCRContent, Dict), + ExtraFileChoosers = find_extra_file_sections(WSCRContent), + case fill_extra_file_inputs(ExtraFileChoosers, CommandRec, St, fun (CommandRec_1) -> + {shape,{shape_from_script, {CommandRec_1, Params}}} + end) of + {file_inputs, _ExtraFiles} -> + case get_wscr_params(WSCRContent) of + {ok, _Title, _, [], []} -> + {shape,{shape_from_script, {CommandRec, []}}}; + {ok, Title, PrevMode, SettableParams, Templates} + when length(SettableParams) > 0; + length(Templates) > 0 -> + make_shape_askdialog( + Params, Title, PrevMode, SettableParams, State_1, + Dict, Templates, CommandRec, St) + end; + ReturnBack -> ReturnBack + end; +make_shape_from_script(ScriptParams, #command_rec{wscrcont=WSCRContent,scrfile=ScriptFileName,scrtype=ScriptType}=CommandRec, St) + when is_list(ScriptParams) -> + case fill_extra_files(find_extra_file_sections(WSCRContent), CommandRec) of + {file_inputs, ExtraFiles} -> + Dict = orddict:from_list([{"st", St}, {"params", ScriptParams}]), + {ParamsSetVars, _} = crun_section("params_set", WSCRContent, Dict), + PSScriptParams_1 = append_extra_files(ExtraFiles, ParamsSetVars), + + ScrSt = #{script_type=>ScriptType, + script_filename=>ScriptFileName, + script_params=>ScriptParams, + more_params=>PSScriptParams_1, + settings=>get_settings_for_run_script()}, + case run_script_once(keep, ScrSt) of + {ok, {{new_shape, Pfx, #e3d_object{}=Obj, Mat}, _}} -> + %% The e3d_file returned needs to be adjusted. + TempFolder = temp_folder(), + {#e3d_file{mat=Mat_1}=_, TempFiles} = + e3df_from_script(#e3d_file{mat=Mat}, TempFolder, []), + delete_temps(TempFolder, TempFiles), + {new_shape, Pfx, Obj, Mat_1}; + {ok, {Return, _}} -> + Return; + {error, Err} -> + {error, Err} + end + end. + + +command_askdialog( + Params, Title, false, SettableParams, State_1, Dict, Templates, Op, + CommandRec, _St) -> + askdialog(Params, Title, + to_dialog_params(SettableParams, State_1, Dict), Templates, + fun(Res) -> {Op,{command_from_script, {CommandRec, Res}}} end); +command_askdialog( + Params, Title, true, SettableParams, State_1, Dict, Templates, Op, + #command_rec{scrfile=ScriptFileName,scrtype=ScriptType}=CommandRec, St) -> + case run_script_w_preview(ScriptType, ScriptFileName, + get_settings_for_run_script()) + of {ok, FP} -> + askdialog_w_prev(Params, Title, + to_dialog_params(SettableParams, State_1, Dict), Templates, + fun(Res) -> {Op,{command_from_script, {CommandRec, Res}}} end, + St, FP) + end. + +command_from_script(Op, Params, #command_rec{wscrcont=WSCRContent}=CommandRec, St) + when is_atom(Params) -> + Dict = orddict:from_list([{"st", St}]), + {_, State_1} = crun_section("params_init", WSCRContent, Dict), + case fill_extra_file_inputs(find_extra_file_sections(WSCRContent), CommandRec, St, fun (CommandRec_1) -> + {Op,{command_from_script, {CommandRec_1, Params}}} + end) of + {file_inputs, _ExtraFiles} -> + case get_wscr_params(WSCRContent) of + {ok, _Title, _, [], []} -> + {Op, {command_from_script, {CommandRec, []}}}; + {ok, Title, PrevMode, SettableParams, Templates} + when length(SettableParams) > 0; + length(Templates) > 0 -> + command_askdialog( + Params, Title, PrevMode, SettableParams, State_1, Dict, + Templates, Op, CommandRec, St) + end; + ReturnBack -> ReturnBack + end; +command_from_script(Op, ScriptParams, #command_rec{wscrcont=WSCRContent, + scrfile=ScriptFileName,scrtype=ScriptType}=CommandRec, St) + when is_list(ScriptParams) -> + + case fill_extra_files(find_extra_file_sections(WSCRContent), CommandRec) of + {file_inputs, ExtraFiles} -> + Dict = orddict:from_list([{"st", St}, {"params", ScriptParams}, {"op", Op}]), + {ParamsSetVars, _} = crun_section("params_set", WSCRContent, Dict), + + %% change_points scripts can be in either 'body' or 'face' selection mode. + %% When in 'body' mode, the whole mesh is changed. + %% When in 'face' mode, selected contains the points inside the face + %% selection (to figure out: if all points of face is included, or only those + %% inside the region selection (all points, less points of the outer edges). + %% + {CmdInputs,CmdChanges} = wscr_command(WSCRContent), + PSScriptParams_1 = append_extra_files(ExtraFiles, ParamsSetVars), + CmdType = {mapfold, CmdInputs, CmdChanges}, + command_from_script_1( + CmdType, ScriptType, ScriptFileName, + Op, ScriptParams, PSScriptParams_1, St) + end. + + +%% Using wings_sel:mapfold/3, the script is called for each We0 object, +%% and a list of changes is applied to We0. +command_from_script_1({mapfold, CmdInputs, CmdChanges}, + ScriptType, ScriptFileName, + Op, ScriptParams, PSScriptParams_1, + #st{selmode=SelMode}=St) -> + {St1, Changed} = wings_sel:mapfold(fun (Items, We0, Changed_0) -> + CmdExtraParams = command_extra_params(CmdInputs, We0), + + ScrSt = #{script_type=>ScriptType, + script_filename=>ScriptFileName, + script_params=>ScriptParams, + more_params=>[ + [<<"op">>, Op], + [<<"selmode">>, SelMode], + [<<"sel">>, gb_sets:to_list(Items)] + | CmdExtraParams ++ PSScriptParams_1], + settings=>get_settings_for_run_script(), + we=>We0,prev_we_stack=>[],st=>St}, + case run_script_once(error, ScrSt) of + {ok, {Return, #{we:=We1}}} -> + Changed_1 = if We0 =/= We1 -> true; true -> Changed_0 end, + SelChanges = sel_changes(SelMode,Items,We0,We1), + command_modifications(Return, CmdChanges, SelChanges, We1, Changed_1); + {error, Err} -> + err(io_lib:format("~p: ~p", [ScriptFileName, Err])), + {We0, Changed_0} + end + end, false, St), + case Changed of + true -> + {save_state, update_selection(St,St1)}; + false -> + St + end. + +%% Update the selection in #st{}. +%% +update_selection(#st{selmode=SelMode,sel=S1,shapes=Shs1}=_St1,#st{shapes=Shs2}=St2) -> + S2 = update_selection_1(SelMode, S1, Shs1, Shs2, []), + St2#st{sel=S2}. +update_selection_1(SelMode, [{Id,Sel}|S1], Shs1, Shs2, S2) -> + #we{}=We1 = gb_trees:get(Id,Shs1), + #we{}=We2 = gb_trees:get(Id,Shs2), + Sel2 = update_selection_2(SelMode, Sel, We1, We2), + update_selection_1(SelMode, S1, Shs1, Shs2, [{Id,Sel2}|S2]); +update_selection_1(_SelMode, [], _Shs1, _Shs2, S2) -> + lists:reverse(S2). + +update_selection_2(body,Sel,_,_) -> + Sel; +update_selection_2(SelMode,Sel,We0,We1) -> + sel_changes_1(SelMode,Sel,We0,We1). + + +%% Get selection and new items to apply changes from +%% set_points, set_face_uvs, set_face_colors +%% +sel_changes(edge,Items,We0,We1) -> + Items2 = sel_changes_1(edge,Items,We0,We1), + {edge,wings_face:from_edges(Items,We1),wings_vertex:from_edges(Items2,We1)}; +sel_changes(face,Items,We0,We1) -> + Items2 = sel_changes_1(face,Items,We0,We1), + {face,Items,wings_vertex:from_faces(Items2,We1)}; +sel_changes(body,_Items,_We0,#we{vp=Vtab,fs=Ftab}=_We1) -> + Items1 = gb_sets:from_list(gb_trees:keys(Ftab)), + Vs = orddict:fetch_keys(array:sparse_to_orddict(Vtab)), + {body,Items1,gb_sets:from_list(Vs)}; +sel_changes(SelMode,Items,We0,We1) -> + Items2 = sel_changes_1(SelMode,Items,We0,We1), + {SelMode,Items2,Items2}. + +%% Get difference in selected items +%% +sel_changes_1(edge,Items,We0,#we{es=Etab}=We1) -> + Etab1 = gb_sets:from_list(orddict:fetch_keys(array:sparse_to_orddict(Etab))), + Items1 = wings_we:new_items_as_gbset(edge, We0, We1), + Items2 = gb_sets:union(gb_sets:intersection(Items,Etab1),Items1), + Items2; +sel_changes_1(face,Items,We0,#we{fs=Ftab}=We1) -> + Ftab1 = gb_sets:from_list(gb_trees:keys(Ftab)), + Items1 = wings_we:new_items_as_gbset(face, We0, We1), + Items2 = gb_sets:union(gb_sets:intersection(Items,Ftab1),Items1), + Items2; +sel_changes_1(SelMode,Items,We0,#we{vp=Vtab}=We1) -> + Vs = gb_sets:from_list(orddict:fetch_keys(array:sparse_to_orddict(Vtab))), + Items1 = wings_we:new_items_as_gbset(SelMode, We0, We1), + gb_sets:union(gb_sets:intersection(Items,Vs),Items1). + + + + +wscr_command(Cont_0) -> + Cont = wscr_map(Cont_0), + CmdChanges_0 = map_find(command_changes, Cont, fun () -> "" end), + CmdChanges_1 = string:tokens(CmdChanges_0, " ,"), + CmdInputs_0 = map_find(command_inputs, Cont, fun () -> CmdChanges_0 end), + CmdInputs_1 = string:tokens(CmdInputs_0, " ,"), + CmdChanges = wscr_command_1(CmdChanges_1), + CmdInputs = wscr_command_1(CmdInputs_1), + {CmdInputs, gb_sets:from_list(CmdChanges)}. +wscr_command_1(List) -> + wscr_command_1(List, []). +wscr_command_1([A|List], OL) -> + A_1 = case A of + "points" -> points; + "faces" -> faces; + "face_colors" -> face_colors; + "face_uvs" -> face_uvs; + "e3d_mesh" -> e3d_mesh; + _ -> unknown + end, + wscr_command_1(List, [A_1|OL]); +wscr_command_1([], OL) -> + [A || A <- lists:reverse(OL), A =/= unknown]. + + +%% Choose extra parameters to send to command scripts +%% +command_extra_params(R, We0) -> + command_extra_params(R, We0, []). +command_extra_params([points | R], #we{vp=Vtab}=We0, EP) -> + Vtab_1 = array:sparse_to_orddict(Vtab), + %% Remove the undefined atoms before sending it to the script. + P = [<<"points">>, Vtab_1], + command_extra_params(R, We0, [P | EP]); +command_extra_params([faces | R], #we{fs=Ftab}=We0, EP) -> + FL = gb_trees:keys(Ftab), + Faces = [{FIdx, { + wings_face:vertices_ccw(FIdx, We0), + bool_none(wings_va:face_attr(color, FIdx, We0)), + bool_none(wings_va:face_attr(uv, FIdx, We0)) + }} || FIdx <- FL], + P = [<<"faces">>, Faces], + command_extra_params(R, We0, [P | EP]); +command_extra_params([face_colors | R], #we{fs=Ftab}=We0, EP) -> + FL = gb_trees:keys(Ftab), + Colors = [{FIdx, bool_none(wings_va:face_attr(color, FIdx, We0))} + || FIdx <- FL], + P = [<<"face_colors">>, Colors], + command_extra_params(R, We0, [P | EP]); +command_extra_params([face_uvs | R], #we{fs=Ftab}=We0, EP) -> + FL = gb_trees:keys(Ftab), + UVs = [{FIdx, bool_none(wings_va:face_attr(uv, FIdx, We0))} + || FIdx <- FL], + P = [<<"face_uvs">>, UVs], + command_extra_params(R, We0, [P | EP]); +command_extra_params([e3d_mesh | R], #we{}=We0, EP) -> + Mesh = sendout_mesh(We0), + P = [<<"e3d_mesh">>, Mesh], + command_extra_params(R, We0, [P | EP]); +command_extra_params([], _We0, EP) -> + lists:reverse(EP). + + +%% Modify the We structure with modifications returned from the command script +%% +command_modifications(Returned, Changeable, SelChanges, We0, Changed) -> + {We1,Changed1} = command_modifications_1(Returned, Changeable, SelChanges, {We0, false}), + {We1,Changed1 or Changed}. +command_modifications_1([[set_points, Vtab_2] | R], Changeable, SelChanges, Ret0) -> + Ret1 = case gb_sets:is_element(points, Changeable) of + false -> + Ret0; + true -> + command_modifications_2(set_points, Vtab_2, SelChanges, Ret0) + end, + command_modifications_1(R, Changeable, SelChanges, Ret1); +command_modifications_1([[set_face_uvs, Vtab_2] | R], Changeable, SelChanges, Ret0) -> + Ret1 = case gb_sets:is_element(face_uvs, Changeable) of + false -> + Ret0; + true -> + command_modifications_2(set_face_uvs, Vtab_2, SelChanges, Ret0) + end, + command_modifications_1(R, Changeable, SelChanges, Ret1); +command_modifications_1([[set_face_colors, Vtab_2] | R], Changeable, SelChanges, Ret0) -> + Ret1 = case gb_sets:is_element(face_colors, Changeable) of + false -> + Ret0; + true -> + command_modifications_2(set_face_colors, Vtab_2, SelChanges, Ret0) + end, + command_modifications_1(R, Changeable, SelChanges, Ret1); +command_modifications_1([[set_e3d_mesh, Mesh_1] | R], Changeable, SelChanges, Ret0) -> + Ret1 = case gb_sets:is_element(e3d_mesh, Changeable) of + false -> + Ret0; + true when is_record(Mesh_1, e3d_mesh) -> + command_modifications_2(set_e3d_mesh, Mesh_1, SelChanges, Ret0); + _ -> + %% Returned face_colors should be exactly the same length as input + warn(?__(4,"Returned e3d_mesh is not a valid record'.")), + Ret0 + end, + command_modifications_1(R, Changeable, SelChanges, Ret1); + +command_modifications_1([], _Changeable, _SelChanges, {_We0, false}=Ret) -> + ?DEBUG_FMT("Did not change:~p~n", [Ret]), + Ret; +command_modifications_1([], _Changeable, _SelChanges, Ret) -> + Ret. + + +command_modifications_2(set_points, Vtab_2, {_,_,SelVs}, {#we{vp=Vtab_1_0}=We0, _}) -> + Vtab_1 = array:sparse_to_orddict(Vtab_1_0), + Vtab_2_1 = array:from_orddict(Vtab_2), + Vtab_3 = orddict:map( + fun (Idx,Val) -> + case gb_sets:is_element(Idx,SelVs) of + true -> + Val0 = array:get(Idx,Vtab_2_1), + if Val0 =/= undefined -> Val0; true -> Val end; + false -> + Val + end + end, Vtab_1), + We1 = We0#we{vp=array:from_orddict(Vtab_3)}, + {We1, true}; +command_modifications_2(set_face_uvs, Vtab_2, {SelMode,SelFaces,_}, {#we{fs=Ftab}=We0, _}) + when SelMode =:= face; SelMode =:= body -> + We1 = lists:foldl(fun({FNum, UVList}, W) -> + case gb_sets:is_element(FNum, SelFaces) of + true -> + case gb_trees:is_defined(FNum, Ftab) of + true -> + wings_va:set_face_attr_vs(uv, FNum, unbool_none(UVList), W); + _ -> + W + end; + false -> + W + end + end, We0, Vtab_2), + {We1, true}; +command_modifications_2(set_face_colors, Vtab_2, {SelMode,SelFaces,_}, {#we{fs=Ftab}=We0, _}) + when SelMode =:= face; SelMode =:= body -> + We1 = lists:foldl(fun({FNum, ColorList}, W) -> + case gb_sets:is_element(FNum, SelFaces) of + true -> + case gb_trees:is_defined(FNum, Ftab) of + true -> + wings_va:set_face_attr_vs(color, FNum, unbool_none(ColorList), W); + _ -> + W + end; + _ -> + W + end + end, We0, Vtab_2), + {We1, true}; +command_modifications_2(set_e3d_mesh, Mesh_1, {body,_,_}, {We0, _}) -> + We1 = merge_we_from_e3d(We0, returned_mesh(Mesh_1)), + {We1, true}; +command_modifications_2(Change, _Mesh_1, _, Ret0) -> + io:format(?__(1,"~w: Returned '~w' values cannot be used to change " + "because of the selection mode."), [?MODULE, Change]), + Ret0. + + + +merge_we_from_e3d(We0, We2) -> + #we{ + es=W1Es,lv=W1Lv,rv=W1Rv,fs=W1Fs,he=W1He,vc=W1Vc,vp=W1Vp, + mat=W1Mat,next_id=W1NextId,holes=W1Holes} = We2, + We0#we{ + es=W1Es,lv=W1Lv,rv=W1Rv,fs=W1Fs,he=W1He,vc=W1Vc,vp=W1Vp, + mat=W1Mat,next_id=W1NextId,mirror=none,holes=W1Holes}. + + +sendout_mesh(We0) -> + wings_export:make_mesh(We0, []). + +returned_mesh(#e3d_mesh{}=Mesh0) -> + Mesh1 = e3d_mesh:merge_vertices(Mesh0), + Mesh2 = e3d_mesh:clean_faces(Mesh1), + Mesh3 = e3d_mesh:transform(Mesh2), + Mesh = e3d_mesh:hard_edges_from_normals(Mesh3), + We0 = wings_import:import_mesh(material, Mesh), + We0. + +bool_none(L) -> + [bool_none_1(A) || A <- L]. +bool_none_1(none) -> false; +bool_none_1(A) -> A. + +unbool_none(L) -> + [unbool_none_1(A) || A <- L]. +unbool_none_1(false) -> none; +unbool_none_1(A) -> A. + + + +import_export_from_script(Op, Params, #command_rec{wscrcont=WSCRContent}=CommandRec, St) + when is_atom(Params) -> + Dict = orddict:from_list([{"st", St}]), + {_, State_1} = crun_section("params_init", WSCRContent, Dict), + case get_wscr_params(WSCRContent) of + {ok, _Title, _, [], []} -> + {file, {Op, {import_export_from_script, {CommandRec, []}}}}; + {ok, Title, _, SettableParams, Templates} + when length(SettableParams) > 0; + length(Templates) > 0 -> + askdialog(Params, Title, + to_dialog_params(SettableParams, State_1, Dict), Templates, + fun(Res) -> {file, {Op,{import_export_from_script, {CommandRec, Res}}}} end) + end; +import_export_from_script(import, ScriptParams, #command_rec{wscrcont=WSCRContent,scrfile=ScriptFileName,scrtype=ScriptType}=CommandRec, St) -> + case fill_extra_file_inputs(find_extra_file_sections(WSCRContent), CommandRec, St, fun (CommandRec_1) -> + {file, {import, {import_export_from_script, {CommandRec_1, ScriptParams}}}} + end) of + {file_inputs, ExtraFiles} -> + Dict = orddict:from_list([{"st", St}, {"params", ScriptParams}]), + {ParamsSetVars, _} = crun_section("params_set", WSCRContent, Dict), + ImportParams_0 = get_map(import_params, ParamsSetVars), + {ScrSpecPs, ImportParams} = lists:partition( + fun script_specific_param/1, maps:to_list(ImportParams_0)), + + case get_wscr_import_export_params(WSCRContent) of + {ok, Extensions, Templates} -> + Props = [{extensions, Extensions} | ImportParams], + wpa:import(Props, fun(F) -> + PSScriptParams_1 = append_extra_files(ExtraFiles, ParamsSetVars), + TempFolder = temp_folder(), + + ScrSt = #{script_type=>ScriptType, + script_filename=>ScriptFileName, + script_params=>ScriptParams, + more_params=>[ [<<"filename">>, binstr(F)], + [<<"temp_folder">>, binstr(TempFolder)] + | PSScriptParams_1], + settings=>get_settings_for_run_script()}, + case run_script_once(error, ScrSt) of + {ok, {ReturnedE3D, _}} -> + case ReturnedE3D of + {ok, #e3d_file{}=ReturnedE3D_1} -> + %% The e3d_file returned needs to be adjusted. + {Ret_1, TempFiles} = e3df_from_script(ReturnedE3D_1, TempFolder, ScrSpecPs), + delete_temps(TempFolder, TempFiles), + {ok, import_transform(Ret_1, ScriptParams, Templates)}; + _ -> + ReturnedE3D + end; + {error, Err} -> {error, Err} + end + end, St) + end; + ReturnBack -> ReturnBack + end; +import_export_from_script(Op, ScriptParams, + #command_rec{wscrcont=WSCRContent, + scrfile=ScriptFileName,scrtype=ScriptType}=CommandRec, St) + when Op =:= export; Op =:= export_selected -> + case fill_extra_file_inputs(find_extra_file_sections(WSCRContent), CommandRec, St, fun (CommandRec_1) -> + {file, {Op, {import_export_from_script, {CommandRec_1, ScriptParams}}}} + end) of + {file_inputs, ExtraFiles} -> + Dict = orddict:from_list([{"st", St}, {"params", ScriptParams}]), + {ParamsSetVars, _} = crun_section("params_set", WSCRContent, Dict), + ExportParams_0 = get_map(export_params, ParamsSetVars), + {ScrSpecPs, ExportParams} = lists:partition( + fun script_specific_param/1, maps:to_list(ExportParams_0)), + + case get_wscr_import_export_params(WSCRContent) of + {ok, Extensions, Templates} -> + Props = [{extensions, Extensions} | ExportParams], + wpa:Op(Props, fun(F, E3DCont_00) -> + E3DCont_0 = mesh_export_changes(export_transform(E3DCont_00, ScriptParams, Templates), Props), + E3DCont = export_texture_options(E3DCont_0, F, ScriptParams, ScrSpecPs), + + PSScriptParams_1 = append_extra_files(ExtraFiles, ParamsSetVars), + TempFolder = temp_folder(), + {E3DCont_1, TempFiles} = e3df_filename_tup(E3DCont, TempFolder, ScrSpecPs), + + ScrSt = #{script_type=>ScriptType, + script_filename=>ScriptFileName, + script_params=>ScriptParams, + more_params=>[ [<<"filename">>, binstr(F)], + [<<"content">>, E3DCont_1], + [<<"temp_folder">>, binstr(TempFolder)] + | PSScriptParams_1], + settings=>get_settings_for_run_script()}, + case run_script_once(error, ScrSt) of + {ok, {Return, _}} -> + delete_temps(TempFolder, TempFiles), + ?DEBUG_FMT("RETURNED: ~p~n", [Return]), + case Return of + {ok} -> ok; + [ok] -> ok; + _ -> {error, "Error"} + end; + {error, Err} -> + delete_temps(TempFolder, TempFiles), + {error, Err} + end + end, St) + end; + ReturnBack -> ReturnBack + end. + +export_texture_options(E3DContent, FileName, Attr, ScrSpecPs) -> + Conv = proplists:get_value(script_texture_convert, ScrSpecPs, bytes), + case Conv of + %% Set from exporter template settings + user -> + Dir = filename:dirname(FileName), + Filetype = proplists:get_value(default_filetype, Attr, ".png"), + E3DContent_1 = wpa:save_images(E3DContent, Dir, Filetype), + E3DContent_1; + %% Something else + _ -> + E3DContent + end. + + + +script_specific_param({script_texture_convert,_}) -> true; +script_specific_param(_) -> false. + + +import_transform(E3dFile, KeyVals, [{import, _}|Templates]) -> + import_transform(import_transform_1(E3dFile, KeyVals), KeyVals, Templates); +import_transform(E3dFile, KeyVals, [_|Templates]) -> + import_transform(E3dFile, KeyVals, Templates); +import_transform(E3dFile, _, []) -> + E3dFile. +import_transform_1(E3dFile, KeyVals) -> + Mat = wpa:import_matrix(KeyVals), + e3d_file:transform(E3dFile, Mat). + +export_transform(E3dFile, KeyVals, [{export,_}|Templates]) -> + export_transform(export_transform_1(E3dFile, KeyVals), KeyVals, Templates); +export_transform(E3dFile, KeyVals, [_|Templates]) -> + export_transform(E3dFile, KeyVals, Templates); +export_transform(E3dFile, _, []) -> + E3dFile. +export_transform_1(E3dFile, KeyVals) -> + Mat = wpa:export_matrix(KeyVals), + e3d_file:transform(E3dFile, Mat). + + +mesh_export_changes(#e3d_file{objs=O}=E3DCont, Props) -> + case proplists:get_value(include_normals, Props, false) of + true -> + O_1 = [mesh_export_changes_1(Mesh) || Mesh <- O], + E3DCont#e3d_file{objs=O_1}; + _ -> + E3DCont + end. +mesh_export_changes_1(#e3d_object{obj=Mesh}=Obj) -> + Mesh_1 = e3d_mesh:vertex_normals(Mesh), + Obj#e3d_object{obj=Mesh_1}. + + + +%% Change the e3d_file tuple a little bit for the script +e3df_filename_tup(#e3d_file{objs=Objs,creator=Creator,dir=Dir,mat=Mats}=E3DCont_0, TempFolder, ScrSpecPs) -> + {NewMats, Temps} = lists:foldl(e3df_filename_tup_fun(TempFolder, ScrSpecPs), {[], []}, Mats), + E3DCont = E3DCont_0#e3d_file{ + objs=[e3df_filename_tup_object(E3DO) || E3DO <- Objs], + mat=lists:reverse(NewMats), + creator=stringtup_if_not_atom(Creator), + dir=stringtup_if_not_atom(Dir) + }, + {E3DCont, Temps}. +e3df_filename_tup_fun(TempFolder, ScrSpecPs) -> + fun({Id, MatAL}, {L1_Mat, L1_Temp}) -> + ListMaps = proplists:get_value(maps, MatAL, []), + + {NewListMaps, NewTemps} = lists:foldl(fun ({MapType, MapVal}, {A_Mat, A_Temp}) -> + case e3df_image(MapVal, length(A_Mat), TempFolder, ScrSpecPs) of + {temp, File, T} -> {[{MapType, T} | A_Mat], [File | A_Temp]}; + {no_temp, T} -> {[{MapType, T} | A_Mat], A_Temp} + end + end, {[], []}, ListMaps), + + {[{Id, orddict:store(maps, NewListMaps, orddict:from_list(MatAL))} | L1_Mat], NewTemps ++ L1_Temp} + end. +e3df_filename_tup_object(#e3d_object{name=Name}=E3DO) -> + E3DO#e3d_object{name=stringtup_if_not_atom(Name)}. +e3df_image(T, Number, TempFolder, ScrSpecPs) -> + Conv = proplists:get_value(script_texture_convert, ScrSpecPs, bytes), + case Conv of + user -> + #e3d_image{filename=FlNm}=T, + T_1=T#e3d_image{filename=liststr_to_binstring(FlNm)}, + Empty = <<>>, + {no_temp, T_1#e3d_image{image=Empty}}; + _ -> + e3df_image_1(T, Number, TempFolder, Conv) + end. +e3df_image_1(#e3d_image{image=ImageBin}=T, Number, TempFolder, Conv) -> + case is_binary(ImageBin) of + true -> + C = abs(erlang:unique_integer()), + FName = "raw_image_" ++ integer_to_list(C) ++ "_" ++ integer_to_list(Number), + TempFile = filename:join(TempFolder, FName), + ok = filelib:ensure_dir(TempFile), + {ok, TempFile_1} = e3df_image_conv(Conv, TempFile, T), + {temp, TempFile, T#e3d_image{image=binstr(TempFile_1)}}; + false -> + {no_temp, T} + end. +e3df_image_conv(".png", TempFile_0, E3DImg) -> + TempFile = TempFile_0 ++ ".png", + ok = e3d__png:save(E3DImg, TempFile), + {ok, TempFile}; +e3df_image_conv(".bmp", TempFile_0, E3DImg) -> + TempFile = TempFile_0 ++ ".bmp", + ok = e3d__bmp:save(E3DImg, TempFile, []), + {ok, TempFile}; +e3df_image_conv(".tga", TempFile_0, E3DImg) -> + TempFile = TempFile_0 ++ ".tga", + ok = e3d__tga:save(E3DImg, TempFile, []), + {ok, TempFile}; +e3df_image_conv(bytes, TempFile, #e3d_image{image=ImageBin}=_) -> + ok = file:write_file(TempFile, ImageBin), + {ok, TempFile}. + +stringtup_if_not_atom(Str) when is_atom(Str) -> Str; +stringtup_if_not_atom(Str) when is_list(Str) -> + {string, liststr_to_binstring(Str)}. + + + +e3df_from_script(#e3d_file{mat=Mats}=ReturnedE3D_0, TempFolder, ScrSpecPs) -> + {NewMats, Temps} = lists:foldl(e3df_from_script_fun(TempFolder, ScrSpecPs), {[], []}, Mats), + ReturnedE3D = ReturnedE3D_0#e3d_file{mat=NewMats}, + {ReturnedE3D, Temps}. +e3df_from_script_fun(TempFolder, ScrSpecPs) -> + fun({Id, MatAL}, {L1_Mat, L1_Temp}) -> + ListMaps = proplists:get_value(maps, MatAL, []), + + {NewListMaps, NewTemps} = lists:foldl(fun ({MapType, MapVal}, {A_Mat, A_Temp}) -> + case e3df_from_script_image(MapVal, TempFolder, ScrSpecPs) of + {temp, File, T} -> {[{MapType, T} | A_Mat], [File | A_Temp]}; + {no_temp, T} -> {[{MapType, T} | A_Mat], A_Temp} + end + end, {[], []}, ListMaps), + + MatAL_1 = orddict:store(maps, NewListMaps, orddict:from_list(MatAL)), + {[{Id, MatAL_1} | L1_Mat], NewTemps ++ L1_Temp} + end. +e3df_from_script_image(T, TempFolder, ScrSpecPs) -> + Conv = proplists:get_value(script_texture_convert, ScrSpecPs, bytes), + e3df_from_script_image_1(Conv, T, TempFolder). +e3df_from_script_image_1(bytes, #e3d_image{image=TempFile_0}=T, TempFolder) -> + TempFile_1 = filename:absname(TempFile_0, TempFolder), + case file:read_file_info(TempFile_1) of + {ok, _} -> + T_1 = e3df_from_script_image_conv(bytes, T, TempFile_1), + case path_contains_dots(TempFile_1) + of + false -> + {temp, TempFile_1, T_1}; + true -> + %% This is not an expected temp file so we won't try to delete it. + {no_temp, T_1} + end; + _ -> + warn(io_lib:format("Filename not found: ~p", [TempFile_1])), + error(e3d_image_raw_filename_not_found) + end; +e3df_from_script_image_1(Conv, #e3d_image{filename=TempFile_1}=T, _TempFolder) -> + case file:read_file_info(TempFile_1) of + {ok, _} -> + T_1 = e3df_from_script_image_conv(Conv, T, TempFile_1), + {no_temp, T_1}; + _ -> + warn(io_lib:format("Filename not found: ~p", [TempFile_1])), + error(e3d_image_filename_not_found) + end. + +path_contains_dots(FilePath) -> + TempFile_Split = filename:split(FilePath), + lists:member(<<"..">>, TempFile_Split) orelse + lists:member(<<".">>, TempFile_Split) orelse + lists:member( ".." , TempFile_Split) orelse + lists:member( "." , TempFile_Split). + + +e3df_from_script_image_conv(auto, T, TempFile) -> + {ok, E3DIm} = get_bitmap_by_ext(TempFile), + merge_e3d_image(E3DIm, T); +e3df_from_script_image_conv(".png", T, TempFile) -> + {ok, E3DIm} = read_png(TempFile), + merge_e3d_image(E3DIm, T); +e3df_from_script_image_conv(".jpg", T, TempFile) -> + {ok, E3DIm} = read_jpeg(TempFile), + merge_e3d_image(E3DIm, T); +e3df_from_script_image_conv(bytes, T, TempFile) -> + {ok, ImageBin} = file:read_file(TempFile), + T#e3d_image{image=ImageBin}. +merge_e3d_image( + #e3d_image{name=NA,extra=EA}=A, + #e3d_image{name=NB,extra=EB}) -> + A#e3d_image{ + name=merge_e3d_image_name(NA,NB), + extra=merge_e3d_image_extra(EA,EB) + }. +merge_e3d_image_name(A, []) -> A; +merge_e3d_image_name(_, A) -> A. +merge_e3d_image_extra(A, []) -> A; +merge_e3d_image_extra(_, A) -> A. + +get_bitmap_by_ext(FilePath) -> + case string:to_lower(filename:extension(FilePath)) of + ".jpeg" -> Ext = ".jpg"; + Ext_0 -> Ext = Ext_0 + end, + case Ext of + ".png" -> + F = fun read_png/1; + ".jpg" -> + F = fun read_jpeg/1; + _ -> + F = fun read_default/1 + end, + F(FilePath). + +read_jpeg(FileName) -> + BlockWxMsgs = wxLogNull:new(), + Ret = read_jpeg_1(FileName), + wxLogNull:destroy(BlockWxMsgs), + Ret. +read_jpeg_1(FileName) -> + Image = wxImage:new(), + case wxImage:loadFile(Image, FileName) of + true -> + E3d = wings_image:wxImage_to_e3d(Image), + wxImage:destroy(Image), + {ok, e3d_image:fix_outtype(FileName, E3d, [])}; + false -> + {error, none} + end. +read_png(FileName) -> + case e3d__png:load(FileName) of + E3D=#e3d_image{} -> + {ok, E3D}; + {error, Err} -> {error, Err} + end. +read_default(FileName) -> + case e3d_image:load(FileName) of + E3DImage=#e3d_image{} -> + {ok, E3DImage}; + {error, Err} -> {error, Err} + end. + + +temp_folder() -> + TempFolder = filename:basedir(user_cache, "wings_" ++ atom_to_list(?MODULE)), + TempFolder. + + +%% Delete temporary files left by e3d_image script interfacing, the files +%% must be inside the expected temporary folder or they won't be deleted. +%% +delete_temps(_TempFolder, []) -> ok; +delete_temps(TempFolder, [_TempFile | TempFiles]) -> + %% TODO: Delete _TempFile + delete_temps(TempFolder, TempFiles). + +get_settings_for_run_script() -> + AllEng = scripting_engines:all_engines(), + lists:append([ + get_settings_for_run_script_eng(Eng, HasArgs) + || {Eng, HasArgs} <- AllEng ]). + +get_settings_for_run_script_eng(_, no_bin) -> []; +get_settings_for_run_script_eng(Eng, HasArgs) + when is_boolean(HasArgs) -> + Atom = list_to_atom("setting_" ++ Eng ++ "_int_path"), + [{Atom, wpa:pref_get(?MODULE, Atom, "")}] ++ + get_settings_for_run_script_eng_1(Eng, HasArgs). + +get_settings_for_run_script_eng_1(_, false) -> []; +get_settings_for_run_script_eng_1(Eng, true) -> + Atom = list_to_atom("setting_" ++ Eng ++ "_arguments"), + [{Atom, wpa:pref_get(?MODULE, Atom, "")}]. + +%%% +%%% + +%% +%% Plugin Scripts +%% +%% Plugin scripts are scripts that get added into wing's menu +%% system so they can be accessed the same way as plugins, this +%% is done by having a config file that points to the wscr file, +%% and some information of the menu to create for the script. +%% + +%% Example plugin script .conf file: +%% +%% {"com.example.my.plugin.1", [ +%% {wscr, "my-plugin.wscr"}, +%% {menu, [ +%% {[tools, newsubmenu, newnewsubmenu, myplugin], []} +%% ]} +%% ]}. +%% +%% {strings, [ +%% {plugin, "com.example.my.plugin.1"}, +%% {"en", [ +%% {{plugin,name}, "My Plugin"}, +%% {{menu,newsubmenu}, "New Submenu"}, +%% {{menu,newnewsubmenu}, "New new Submenu"}, +%% {{menu,myplugin}, "My Plugin"}, +%% {{info,myplugin}, "My Plugin menu tooltip"} +%% ]} +%% ]}. +%% + + +pluginscripts_make_menu(Tuple, Menu, L) -> + case proplists:get_value(menus, L, undefined) of + Menus when is_map(Menus) -> + case maps:find(Tuple, Menus) of + {ok, List} when is_list(List) -> + Menu ++ [Do || {_,Do} <- List]; + _ -> + Menu + end; + _ -> + Menu + end. + +pluginscripts_command({Op0,_}=Tuple, St, L) -> + Op = case Op0 of + {file, {Op1, _}} -> Op1; + _ -> Op0 + end, + case proplists:get_value(command_find, L) of + Start when is_map(Start) -> + pluginscripts_command_1(Tuple, Start, L, Op, St); + _ -> + next + end. +pluginscripts_command_1({Item,{{wscr,folder},WSCRFile}}, C, _L, Op, St) -> + case maps:find(Item, C) of + {ok, _Next} -> + select_script(script_info(WSCRFile, Op), from_script_fun(Op, St)); + _ -> + next + end; +pluginscripts_command_1({Item,{Item2,_}=Tuple}, C, L, Op, St) when is_atom(Item2) -> + case maps:find(Item, C) of + {ok, Next} -> + pluginscripts_command_1(Tuple, Next, L, Op, St); + _ -> + next + end; +pluginscripts_command_1({Item,Action}, C, L, Op, St) when is_atom(Action) -> + case maps:find(Item, C) of + {ok, _} when Action =:= ignore -> + keep; + {ok, Next} -> + case maps:find(Action, Next) of + {ok, Plugin} -> + pluginscripts_command_2(Plugin, L, Op, St); + _ -> + next + end; + _ -> + next + end; +pluginscripts_command_1(_, _C, _L, _Op, _St) -> + next. + +pluginscripts_command_2({Plugin,_Ask}, L, Op, St) when is_atom(Plugin) -> + case command_map_find(plugins, Plugin, L) of + {ok, WSCRFile} -> + select_script(script_info(WSCRFile, Op), from_script_fun(Op, St)); + _ -> + next + end. + +command_map_find(Atom, Find, L) -> + case proplists:get_value(Atom, L, undefined) of + M when is_map(M) -> + maps:find(Find, M); + _ -> + undefined + end. + + +-record(pluginscripts,{ + list=[], + menus=[], + strings=[] +}). + +get_pluginscripts(Dirnames) -> + get_pluginscripts(Dirnames, #pluginscripts{}). +get_pluginscripts([Dirname|L], Acc0) -> + case file:list_dir(Dirname) of + {ok, List1} when is_list(List1) -> + Acc2=lists:foldl( + fun(F, Acc1) -> + read_conf(filename:join(Dirname,F), Acc1, fun pluginscripts_conf/3) + end, Acc0, List1), + get_pluginscripts(L, Acc2); + _ -> + get_pluginscripts(L, Acc0) + end; +get_pluginscripts([], #pluginscripts{list=[]}) -> + {[], []}; +get_pluginscripts([], #pluginscripts{list=L1,menus=L3,strings=L2}) -> + Strings = maps:from_list( + [{Key, maps:from_list(Val)} + || {Key,Val} <- L2]), + + L3_1 = lists:foldl(fun ({Plugin, [{Menu,Opts}]}, Acc) -> + [{Menu, {Opts, Plugin, maps:get(Plugin, Strings)}}|Acc] + end, [], L3), + + {[{plugins, maps:from_list(L1)}], L3_1}. + +get_pluginscripts_1(L3_0, L) -> + L3 = lists:usort(L3_0), + %% Generate Menu Constructions + MenuC = pluginscripts_menuc(L3), + %% Generate Command Finders + CommandFinder = pluginscripts_cmdf(L3), + [ + {menus, maps:from_list(MenuC)}, + {command_find, CommandFinder} + | L ]. + + +%% Menu Constructions +pluginscripts_menuc(L3) -> + MenuC_0 = lists:foldl(fun ({Menu, {Opts, Plugin, PluginStrings}}, Acc) -> + Ret = pluginscripts_menus( + Menu, {Opts, Plugin, PluginStrings}), + [[T || T <- Ret, is_tuple(T)]|Acc] + end, [], L3), + MenuC = lists:usort(lists:append(MenuC_0)), + pluginscripts_menuc_1(MenuC, []). + +%% Regroup commands together into lists for each submenu. +pluginscripts_menuc_1([{A,B1}|L], [{A,B2}|OL]) -> + pluginscripts_menuc_1(L, [{A,[B1|B2]}|OL]); +pluginscripts_menuc_1([{A,B1}|L], OL) -> + pluginscripts_menuc_1(L, [{A,[B1]}|OL]); +pluginscripts_menuc_1([], OL) -> + lists:reverse([{A,lists:reverse(B)} || {A,B} <- OL]). + +%% Generate command finders, which is a hierarchical map of +%% submenu atoms that is matched against when command/2 is +%% called. +pluginscripts_cmdf(L3) -> + lists:foldl(fun ({Menu, {Opts, Plugin, _PluginStrings}}, Acc) -> + pluginscripts_command_find(Menu, Opts, Plugin, Acc) + end, #{}, L3). + +read_conf(Filename, Acc, F) -> + case filelib:is_regular(Filename) of + true -> + Ext = filename:extension(Filename), + case string:lowercase(Ext) =:= ".conf" of + true -> + {ok, Cont} = file:consult(Filename), + F(Cont, filename:dirname(Filename), Acc); + _ -> + Acc + end; + _ -> + Acc + end. + + +pluginscripts_conf(Cont, Dirname, Acc0) -> + F = fun + ({strings, Info}, #pluginscripts{strings=Strings}=Acc) -> + Acc#pluginscripts{strings=read_conf_strings(plugin, Info, Strings)}; + ({[C|_]=Name, Info}, Acc) when is_integer(C) -> + pluginscripts_conf_push(Info, Name, Dirname, Acc) + end, + lists:foldl(F, Acc0, Cont). +pluginscripts_conf_push(Info, Name, Dirname, #pluginscripts{list=L1,menus=L2}=Acc) -> + Atom = list_to_atom(Name), + %% The .wscr is always a relative path to the .conf file. + Item={Atom,filename:join(Dirname, proplists:get_value(wscr, Info))}, + MItem={Atom,proplists:get_value(menu, Info)}, + Acc#pluginscripts{list=[Item|L1],menus=[MItem|L2]}. + +read_conf_strings(Atom, Info, Strings) -> + {Langs, Info1} = lists:partition( + fun + ({A,_}) when is_list(A) -> true; + (_) -> false + end, Info), + Plugin = proplists:get_value(Atom, Info1), + orddict:append_list(list_to_atom(Plugin),Langs,Strings). + +pluginscripts_menus([Op|_]=MenuList,Command) -> + CreateLists = pluginscripts_menus_3(MenuList), + [pluginscripts_menus_2(M,Command) || M <- CreateLists] ++ + [pluginscripts_menus_1(Op, lists:reverse(MenuList),Command)]. + +pluginscripts_menus_1(Op, [Command|List],{Opts,_Plugin,Strings}) -> + Lang = current_lang_code(), + Str1 = case pluginscripts_lang({menu,Command}, Lang, Strings) of + false -> atom_to_list(Command); + "" -> atom_to_list(Command); + Str1_1 -> Str1_1 + end, + Str2 = case pluginscripts_lang({info,Command}, Lang, Strings) of + false -> Str1; + "" -> Str1; + Str2_1 -> Str2_1 + end, + Command_1 = case Opts of + [{folder,FolderList}] -> + menu_script_folder(Op,Command,Str1,FolderList); + _ -> + {Str1, Command, Str2} + end, + {list_to_tuple(lists:reverse(List)), {command, Command_1}}. + +menu_script_folder(Op,Atom,Name,FolderList) -> + F = fun + (help, _Ns) -> + {?__(1,"Open Script Folder"), + "", + ""}; + (1, _Ns) -> menu_script_folder_1(Op,Atom,FolderList); + (2, _Ns) -> ignore; + (3, _Ns) -> menu_script_folder_1(Op,Atom,FolderList) + end, + {Name,{Atom,F}}. + +menu_script_folder_1(Op,Atom,FolderList) -> + List3 = lists:foldl(fun({_Dir, SList},List1) -> + lists:foldl(fun(WSCR, List2) -> + [menu_script_folder_2(Op,Atom,WSCR)|List2] + end, List1, SList) + end, [], read_script_dirs(FolderList, Op)), + case List3 of + [] -> + [{?__(1,"No scripts available"),ignore,?__(2,"None")}]; + _ -> + List3 + end. + +menu_script_folder_2(Op,Atom,{WSCRFile, {_ScriptFile, _ScriptType, ScriptName, ScriptDesc}}) -> + F = fun + (help, _Ns) -> + {ScriptDesc, + "", + ""}; + (1, _Ns) -> {Op,{Atom,{{wscr,folder},WSCRFile}}}; + (2, _Ns) -> ignore; + (3, _Ns) -> ignore + end, + {ScriptName,{Atom,F}}. + + + +%% Submenus +pluginscripts_menus_2([_],_) -> + false; +pluginscripts_menus_2([SubMenu|List_1],{_Opts,_Plugin,Strings}) -> + Lang = current_lang_code(), + Str1 = case pluginscripts_lang({menu,SubMenu}, Lang, Strings) of + false -> atom_to_list(SubMenu); + Str1_1 -> Str1_1 + end, + {list_to_tuple(lists:reverse(List_1)), {submenu, {Str1,{SubMenu,[]}}}}. + +pluginscripts_menus_3(List) -> + [_|List1] = lists:reverse(List), + pluginscripts_menus_3(List1, []). +pluginscripts_menus_3([_|List]=Full, OL) -> + pluginscripts_menus_3(List, [Full|OL]); +pluginscripts_menus_3([], OL) -> + OL. + +pluginscripts_lang(Key, Lang, Strings) -> + case pluginscripts_lang_1(Key, Lang, Strings) of + false -> + case pluginscripts_lang_1(Key, "en", Strings) of + false -> + false; + Str -> + Str + end; + Str -> + Str + end. +pluginscripts_lang_1(Key, Lang, Strings) -> + case maps:find(Lang, Strings) of + {ok, List} -> + proplists:get_value(Key, List, false); + _ -> + false + end. + + +pluginscripts_command_find([M], Opts, Plugin, Acc) -> + Acc#{M => {Plugin,Opts}}; +pluginscripts_command_find([M|Menu], Opts, Plugin, Acc) -> + case maps:find(M, Acc) of + {ok, Found} -> + Acc#{M => pluginscripts_command_find(Menu, Opts, Plugin, Found)}; + _ -> + Acc#{M => pluginscripts_command_find(Menu, Opts, Plugin, #{})} + end. + +%%% +%%% + +%% +%% Script Folders +%% +%% Script folder files are config files in the user data +%% area where other software or the user can add +%% new paths to find scripts. +%% + +%% Example script folder .conf file: +%% +%% {"com.example.wings-scripts-1",[ +%% {type,[shape,face,body]}, +%% {path,["a1/","a2/"]} +%% ]}. +%% +%% {"com.example.scripts.2",[ +%% {type,[shape]}, +%% {path,["a/"]} +%% ]}. +%% + +-record(scriptfolders,{ + folders_op=[], + folders=[], + strings=[] +}). + +get_scriptfolders(Dirnames) -> + get_scriptfolders(Dirnames, #scriptfolders{}). + +get_scriptfolders([Dirname|L], Acc0) -> + case file:list_dir(Dirname) of + {ok, List} when is_list(List) -> + Acc2=lists:foldl( + fun(F, Acc1) -> + read_conf(filename:join(Dirname,F), Acc1, fun scriptfolder_conf/3) + end, Acc0, List), + get_scriptfolders(L, Acc2); + _ -> + get_scriptfolders(L, Acc0) + end; +get_scriptfolders([], #scriptfolders{folders=[]}) -> + {[],[]}; +get_scriptfolders([], #scriptfolders{folders=L1,folders_op=L2,strings=L3_0}) -> + L3 = get_scriptfolders_addname(L1, L3_0), + + Strings = maps:from_list( + [{Key, maps:from_list([{Key2, get_scriptfolders_3(Val2, Key)} || {Key2,Val2} <- Val])} + || {Key,Val} <- L3]), + + L1_1 = maps:from_list(L1), + + L2_1 = lists:foldl(fun ({Op, List2}, Acc) -> + lists:foldl(fun (Plugin, Acc2) -> + #{Plugin:=FolderList}=L1_1, + [{[Op,Plugin], {[{folder,FolderList}], Plugin, maps:get(Plugin, Strings)}}|Acc2] + end, Acc, List2) + end, [], L2), + + {[{folders, L1_1}], L2_1}. + +%% Add a directory name if there are no strings. +get_scriptfolders_addname(L1, L3_0) -> + Lang = current_lang_code(), + lists:foldl(fun({Key1,[Dir|_]}, L3_1) -> + case proplists:get_value(Key1, L3_1) of + undefined -> + DirName = filename:basename(Dir), + [{Key1,[ + {"en",[{{folder,name},DirName}]}, + {Lang,[{{folder,name},DirName}]} + ]}|L3_1]; + [_ | _] -> + L3_1 + end + end, L3_0, L1). + + +get_scriptfolders_3(Val, Name) -> + lists:map( + fun + ({{folder,name},V}) -> {{menu,Name},V}; + ({Key,V}) -> {Key,V} + end, + Val). + +scriptfolder_conf(Cont, Dirname, Acc0) -> + F = fun + ({strings, Info}, #scriptfolders{strings=Strings}=Acc) -> + Acc#scriptfolders{strings=read_conf_strings(folder, Info, Strings)}; + ({[C|_]=Name, Info}, Acc) when is_integer(C) -> + List0 = proplists:get_value(path, Info, []), + Type = proplists:get_value(type, Info, []), + List = [scriptfolder_path(Dirname, Path) || Path <- List0], + read_scriptfolder_conf_push(Type, List, list_to_atom(Name), Acc) + end, + lists:foldl(F, Acc0, Cont). +read_scriptfolder_conf_push(Type0, List, Name, #scriptfolders{folders=Items}=Acc0) + when length(List) > 0 -> + Types = [atom_to_list(Type) || Type <- Type0], + Item={Name,List}, + lists:foldl( + fun (Type, #scriptfolders{folders_op=Folders}=Acc) -> + Atom = list_to_atom(string:lowercase(string:trim(Type))), + Acc#scriptfolders{folders_op=orddict:append(Atom,Name,Folders)} + end, Acc0#scriptfolders{folders=[Item|Items]}, Types). + +%% Only use relative paths (relative to the .conf file), if the path +%% starts with ./ or ../ + +scriptfolder_path(Dirname, Path0) -> + Path=lists:append(string:replace(Path0, "\\", "/", all)), + scriptfolder_path_1(Dirname, Path). +scriptfolder_path_1(Dirname, "./"++_=Path) -> + filename:join(Dirname, Path); +scriptfolder_path_1(Dirname, "../"++_=Path) -> + filename:join(Dirname, Path); +scriptfolder_path_1(_, [C,$:|_]=Path) + when C >= $A, C =< $Z; C >= $a, C =< $z -> + Path; +scriptfolder_path_1(_, "/"++_=Path) -> + Path; +scriptfolder_path_1(_, Path) -> + case filename:pathtype(Path) of + absolute -> + Path; + _ -> + filename:join(os:getenv("HOME"), Path) + end. + +%%% +%%% + + +%% WSCR Configuration Statements +%% +%% Statements to add specific variables for use by the script, +%% add configuation keys to the wings:import and wings:export +%% functions, and load and save preference on a per file or +%% global context. +%% + +crun_section(Name, WSCRCont, Dict) -> + crun_section(Name, WSCRCont, Dict, #crun_state{}). + +crun_section(_, [], _, State_1) -> + {#{}, State_1}; +crun_section(Name, [[Name, W1] | _], Dict, State) + when is_list(W1) -> + {_, #crun_state{sett_vars=SettVars}=State_1} = crun_list(W1, State, Dict), + {SettVars, State_1}; +crun_section(Name, [_ | Rest_WSCRCont], Dict, State) -> + crun_section(Name, Rest_WSCRCont, Dict, State). + +find_extra_file_sections(WSCRCont) -> + find_extra_file_sections("extra_file", WSCRCont). + +find_extra_file_sections(Name, WSCRCont) -> + find_extra_file_sections(Name, WSCRCont, []). + +find_extra_file_sections(_, [], OSections) -> + lists:reverse(OSections); +find_extra_file_sections(Name, [[Name, FileVarName, SubSections] | List], OSections) + when is_list(SubSections) -> + Section = extra_file_section(SubSections), + OSections_1 = [{FileVarName, Section} | OSections], + find_extra_file_sections(Name, List, OSections_1); +find_extra_file_sections(Name, [_ | Rest_WSCRCont], OSections) -> + find_extra_file_sections(Name, Rest_WSCRCont, OSections). + + +extra_file_section(OSections) -> + extra_file_section(OSections, []). + +extra_file_section([], OSections) -> + lists:reverse(OSections); +extra_file_section([["title", Title] | List], OSections) + when is_list(Title) -> + OSections_1 = orddict:store(title, Title, OSections), + extra_file_section(List, OSections_1); +extra_file_section([["extensions", Extensions] | List], OSections) + when is_list(Extensions) -> + OSections_1 = orddict:store(exts, ["extensions", Extensions], OSections), + extra_file_section(List, OSections_1); +extra_file_section([_ | Rest_WSCRCont], OSections) -> + extra_file_section(Rest_WSCRCont, OSections). + + +%%% +%%% + +crun("ifdef", [PV, OtherCode], State, Dict) -> + {PVRes, State_1} = crun_pv(PV, State, Dict), + if + PVRes =/= error -> + crun_list(OtherCode, State_1, Dict); + true -> + {ok, State_1} + end; + +crun("ifndef", [PV, OtherCode], State, Dict) -> + {PVRes, State_1} = crun_pv(PV, State, Dict), + if + PVRes =:= error -> + crun_list(OtherCode, State_1, Dict); + true -> + {ok, State_1} + end; + +crun("if", [PV1, Op, PV2, OtherCode], State, Dict) + when Op =:= "eq" ; Op =:= "ne" ; + Op =:= "gt" ; Op =:= "lt" ; + Op =:= "ge" ; Op =:= "le" -> + {PV1Res, State_1} = crun_pv(PV1, State, Dict), + {PV2Res, State_2} = crun_pv(PV2, State_1, Dict), + case case Op of + "eq" -> PV1Res =:= PV2Res; + "ne" -> PV1Res =/= PV2Res; + "gt" -> PV1Res > PV2Res; + "lt" -> PV1Res < PV2Res; + "ge" -> PV1Res >= PV2Res; + "le" -> PV1Res =< PV2Res; + _ -> false + end of + true -> crun_list(OtherCode, State_2, Dict); + false -> {ok, State_2} + end; + +crun("do", [PV], State, Dict) -> + {PVRes, State_1} = crun_pv(PV, State, Dict), + ?DEBUG_FMT("do ~p~n", [PVRes]), + {PVRes, State_1}; + +crun("script_param", [K_0, PV], State, Dict) -> + {K, State_1} = crun_pv(K_0, State, Dict), + {PVRes, #crun_state{sett_vars=SettVars}=State_2} = crun_pv(PV, State_1, Dict), + ?DEBUG_FMT("script_param ~s ~p~n", [K, PVRes]), + ScriptParams = get_map(script_params, SettVars), + ScriptParams_1 = ScriptParams#{K=>PVRes}, + {PVRes, State_2#crun_state{sett_vars=SettVars#{script_params=>ScriptParams_1}}}; + +crun("import_param", [K_0, PV], State, Dict) -> + {K, State_1} = crun_pv(K_0, State, Dict), + {PVRes, #crun_state{sett_vars=SettVars}=State_2} = crun_pv(PV, State_1, Dict), + %% Add to wings:import parameters key K from path value PV + ?DEBUG_FMT("import_param ~s ~p~n", [K, PVRes]), + ImportParams = get_map(import_params, SettVars), + ImportParams_1 = ImportParams#{list_to_atom(K)=>PVRes}, + {PVRes, State_2#crun_state{sett_vars=SettVars#{import_params=>ImportParams_1}}}; + +crun("export_param", [K_0, PV], State, Dict) -> + {K, State_1} = crun_pv(K_0, State, Dict), + {PVRes, #crun_state{sett_vars=SettVars}=State_2} = crun_pv(PV, State_1, Dict), + %% Add to wings:export parameters key K from path value PV + ?DEBUG_FMT("export_param ~s ~p~n", [K, PVRes]), + ExportParams = get_map(export_params, SettVars), + ExportParams_1 = ExportParams#{list_to_atom(K)=>PVRes}, + {PVRes, State_2#crun_state{sett_vars=SettVars#{export_params=>ExportParams_1}}}; + +crun("save_pref", [K_0, PV], State, Dict) -> + {K, State_1} = crun_pv(K_0, State, Dict), + {PVRes, State_2} = crun_pv(PV, State_1, Dict), + %% Save into preference from path value PV + io:format("TODO: save_pref ~s ~p~n", [K, PVRes]), + {PVRes, State_2}; + +crun("load_pref", [K, VS, PVDefault], State, Dict) -> + case false of + false -> + {_PVRes, _State} = crun_pv(PVDefault, State, Dict) + + end, + + %% Load from preference and into value slot VS + io:format("TODO: load_pref ~s ~p~n", [K, VS]), + State_1 = State, + {ok, State_1}. + +%% Evaluate a path value +crun_pv({etp,PV}, State, Dict) -> + %% If the special %[...] syntax was used then evaluate the + %% the contents based on the query mini language. + T = etp_tok(PV, [], []), + {PPV, []} = etp_start_parse(T), + etp_run(PPV, State, Dict); +crun_pv({atom,Atom}, State, _Dict) -> + {list_to_atom(Atom), State}; +crun_pv(PV, State, _Dict) -> + Val = case string:to_float(PV) of + {Num_0, []} -> Num_0; + _ -> + case string:to_integer(PV) of + {Num_0, []} -> Num_0; + _ -> PV + end + end, + {Val, State}. + + +crun_list([], State, _Dict) -> + {ok, State}; +crun_list([C | List], State, Dict) -> + [C0 | CArgs] = C, + {_, State_1} = crun(C0, CArgs, State, Dict), + crun_list(List, State_1, Dict). + +get_map(Key, SettVars) when is_atom(Key), is_map(SettVars) -> + case maps:find(Key, SettVars) of + {ok, Map} -> Map; + _ -> #{} + end. + + + +%% WSCR term path notation (when using %[...]) + +%% Path starts with the name of the variable to traverse +%% Options are: st + +%% {N} Get the Nth tuple element +%% /Name As an orddict, find Name +%% [N] Get the Nth list element +%% Mod:Name(...) Call Mod:Name with arguments +%% >$N Store current value into $N +%% <$N Get value from $N +%% tuple(...) Construct tuple with arguments +%% list(...) Construct list with arguments +%% map(.., ...) Map to the inside code (1st arg) the list (2nd arg) +%% filter(.., ..) Filter to the inside code (1st arg) the list (2nd arg) +%% void(..., ...) Run the first argument only for its side effects and return the second argument +%% foldl(..., ..., ...) +%% foldr(..., ..., ...) +%% '...' Atom +%% "..." String +%% "," Separator + +%% Run etp +etp_run([], State, _Dict) -> + {State#crun_state.p, State}; +etp_run([{start,{integer,N}} | R], State, Dict) -> + etp_run(R, State#crun_state{p=N}, Dict); +etp_run([{start,{float,N}} | R], State, Dict) -> + etp_run(R, State#crun_state{p=N}, Dict); +etp_run([{start,{string,N}} | R], State, Dict) -> + etp_run(R, State#crun_state{p=unbinstr(N)}, Dict); +etp_run([{start,{atom,N}} | R], State, Dict) when is_binary(N) -> + etp_run(R, State#crun_state{p=list_to_atom(unbinstr(N))}, Dict); +etp_run([{start,{call,Mod,Name,Args}} | R], State, Dict) -> + %% Our config path language is lazy evaluated, evaluate all the arguments first + {Args_2, State_3} = etp_map_args(Args, State, Dict), + %% We actually call an erlang function here. + Return = apply(list_to_atom(Mod), list_to_atom(Name), lists:reverse(Args_2)), + etp_run(R, State_3#crun_state{p=Return}, Dict); +etp_run([{start,{call,Name,Args}} | R], State, Dict) -> + {Return, State_1} = etp_run_call(Name, Args, State, Dict), + etp_run(R, State_1#crun_state{p=Return}, Dict); +etp_run([{start,{word,Name}} | R], State, Dict) -> + case orddict:find(Name, Dict) of + {ok, V} -> + etp_run(R, State#crun_state{p=V}, Dict); + _ -> + {error, State} + end; + +etp_run([{start,{get_from,Name}} | R], #crun_state{temp_vars=TempVars}=State, Dict) -> + case maps:find(Name, TempVars) of + {ok, V} -> + etp_run(R, State#crun_state{p=V}, Dict); + _ -> + {error, State} + end; + +etp_run([{rec_f, FieldName} | R], State, Dict) -> + case tuple_n_from_field(State#crun_state.p, FieldName) of + {ok, N} -> + if + N < tuple_size(State#crun_state.p) -> + V_1 = element(N+1, State#crun_state.p), + etp_run(R, State#crun_state{p=V_1}, Dict); + true -> {error, State} + end; + _ -> {error, State} + end; +etp_run([{tuple_nth,Ae} | R], State, Dict) -> + {N, _State_1} = etp_get_integer(Ae, State, Dict), + if + N < tuple_size(State#crun_state.p) -> + V_1 = element(N+1, State#crun_state.p), + etp_run(R, State#crun_state{p=V_1}, Dict); + true -> {error, State} + end; +etp_run([{list_nth,Ae} | R], State, Dict) -> + {N, _State_1} = etp_get_integer(Ae, State, Dict), + V_1 = lists:nth(N, State#crun_state.p), + etp_run(R, State#crun_state{p=V_1}, Dict); +etp_run([{orddict_f,Ae} | R], State, Dict) -> + N = etp_get_term(Ae, State, Dict), + if + is_list(State#crun_state.p) -> + try + V_1 = proplists:get_value(N, State#crun_state.p, error), + etp_run(R, State#crun_state{p=V_1}, Dict) + catch _ -> + {error, State} + end; + true -> {error, State} + end; +etp_run([{store_to, K} | R], State, Dict) -> + State_2 = etp_store_temp(K, State#crun_state.p, State), + etp_run(R, State_2, Dict). + +etp_get_integer([{start,_}]=C, State, Dict) -> + case etp_run(C, State, Dict) of + {Ret, State_1} when is_integer(Ret) -> + {Ret, State_1} + end. + +etp_get_term({word,Word}, _State, _Dict) -> + list_to_atom(Word); +etp_get_term([{start,_}]=C, State, Dict) -> + case etp_run(C, State, Dict) of + {Ret, State_1} when is_binary(Ret) -> + {list_to_atom(binary_to_list(Ret)), State_1}; + {Ret, State_1} when is_atom(Ret) -> + {Ret, State_1} + end. + +etp_map_args(Args, State, Dict) -> + lists:foldl(fun(A, {Args_1, State_1}) -> + {Ret_1, State_2} = etp_run(A, State_1, Dict), + {[Ret_1|Args_1], State_2} + end, {[], State}, Args). + +etp_run_call("list", Args, State, Dict) -> + {Args_2, State_3} = etp_map_args(Args, State, Dict), + {lists:reverse(Args_2), State_3}; + +etp_run_call("tuple", Args, State, Dict) -> + {Args_2, State_3} = etp_map_args(Args, State, Dict), + {list_to_tuple(lists:reverse(Args_2)), State_3}; + +etp_run_call("bool", Args, State, Dict) -> + {[Arg1 | _], State_3} = etp_map_args(Args, State, Dict), + Arg1Bool = case Arg1 of + Arg1 when is_integer(Arg1), (Arg1 > 0) orelse (Arg1 =:= -1) -> true; + Arg1 when is_float(Arg1), Arg1 >= 1.0 -> true; + 0 -> false; + Zero when abs(Zero) < ?EPSILON -> false; + Str when is_list(Str) -> + case Str of + [$t | _] -> true; + [$T | _] -> true; + _ -> false + end; + _ -> false + end, + {Arg1Bool, State_3}; + +etp_run_call("int", Args, State, Dict) -> + {[Arg1 | _], State_3} = etp_map_args(Args, State, Dict), + Arg1Int = case Arg1 of + Arg1 when is_integer(Arg1) -> Arg1; + Arg1 when is_float(Arg1) -> round(Arg1); + true -> 1; + false -> 0; + Str when is_list(Str) -> + case string:to_float(Str) of + {Num_F, _} when is_float(Num_F) -> round(Num_F); + _ -> + case string:to_integer(Str) of + {Num_I, _} when is_integer(Num_I) -> Num_I; + _ -> 0 + end + end; + _ -> 0 + end, + {Arg1Int, State_3}; + +etp_run_call("float", Args, State, Dict) -> + {[Arg1 | _], State_3} = etp_map_args(Args, State, Dict), + Arg1Float = case Arg1 of + Arg1 when is_integer(Arg1) -> float(Arg1); + Arg1 when is_float(Arg1) -> Arg1; + true -> 1.0; + false -> +0.0; + Str when is_list(Str) -> + case string:to_float(Str) of + {Num_F, _} when is_float(Num_F) -> Num_F; + _ -> + case string:to_integer(Str) of + {Num_I, _} when is_integer(Num_I) -> float(Num_I); + _ -> +0.0 + end + end; + _ -> +0.0 + end, + {Arg1Float, State_3}; + +etp_run_call("ok_test", [Arg1, Arg2 | _], State, Dict) -> + {Ret_1, State_1} = etp_run(Arg1, State, Dict), + case Ret_1 of + {ok, Ret_1_OkValue} -> + {Ret_1_OkValue, State_1}; + _ -> + etp_run(Arg2, State_1, Dict) + end; +etp_run_call("ok_test", [Arg1], State, Dict) -> + {Ret_1, State_1} = etp_run(Arg1, State, Dict), + case Ret_1 of + {ok, Ret_1_OkValue} -> + {Ret_1_OkValue, State_1}; + _ -> + {error, State_1} + end; + +etp_run_call("value_test", [ArgAtom, Arg1, Arg2 | _], State, Dict) -> + {AtomTest_2, _} = etp_run(ArgAtom, State, Dict), + {Ret_1, State_1} = etp_run(Arg1, State, Dict), + case Ret_1 of + {AtomTest_1, Ret_1_OkValue} when AtomTest_1 =:= AtomTest_2 -> + {Ret_1_OkValue, State_1}; + _ -> + etp_run(Arg2, State_1, Dict) + end; +etp_run_call("value_test", [ArgAtom, Arg1], State, Dict) -> + {AtomTest_2, _} = etp_run(ArgAtom, State, Dict), + {Ret_1, State_1} = etp_run(Arg1, State, Dict), + case Ret_1 of + {AtomTest_1, Ret_1_OkValue} when AtomTest_1 =:= AtomTest_2 -> + {Ret_1_OkValue, State_1}; + _ -> + {error, State_1} + end; + +etp_run_call("if", [ArgTest, ArgThen, ArgElse | _], State, Dict) -> + {AtomTest_2, State_1} = etp_run(ArgTest, State, Dict), + case AtomTest_2 of + true -> + etp_run(ArgThen, State_1, Dict); + _ -> + etp_run(ArgElse, State_1, Dict) + end; +etp_run_call("if", [ArgTest, ArgThen], State, Dict) -> + {AtomTest_2, State_1} = etp_run(ArgTest, State, Dict), + case AtomTest_2 of + true -> + etp_run(ArgThen, State_1, Dict); + _ -> + {false, State_1} + end; + + + + +etp_run_call("void", Args, State, Dict) -> + lists:foldl(fun(A, {_, State_1}) -> + etp_run(A, State_1, Dict) + end, {[], State}, Args); + +etp_run_call("map", Args, State_0, Dict) -> + [DoEr, ListArg | _] = Args, + {List_1, State} = etp_run(ListArg, State_0, Dict), + {Args_2, State_3} = lists:foldl(fun(A, {Args_1, #crun_state{temp_vars=TempVars}=State_1}) -> + Save_0 = etp_get_temp_var(0, TempVars), + State_1_1 = etp_store_temp(0, A, State_1), + {Ret_1, State_2} = etp_run(DoEr, State_1_1#crun_state{p=A}, Dict), + State_2_1 = etp_store_temp(0, Save_0, State_2), + {[Ret_1|Args_1], State_2_1} + end, {[], State}, List_1), + {Args_2, State_3}; + +etp_run_call("filter", Args, State_0, Dict) -> + [DoEr, ListArg | _] = Args, + {List_1, State} = etp_run(ListArg, State_0, Dict), + {Args_2, State_3} = lists:foldl(fun(A, {Args_1, #crun_state{temp_vars=TempVars}=State_1}) -> + Save_0 = etp_get_temp_var(0, TempVars), + State_1_1 = etp_store_temp(0, A, State_1), + {Ret_1, State_2} = etp_run(DoEr, State_1_1#crun_state{p=A}, Dict), + State_2_1 = etp_store_temp(0, Save_0, State_2), + {[Ret_1|Args_1], State_2_1} + end, {[], State}, List_1), + {Args_2, State_3}; + +etp_run_call("foldl", Args, State_0, Dict) -> + [DoEr, ListArg | _] = Args, + {List_1, State} = etp_run(ListArg, State_0, Dict), + {Ret_2, State_3} = lists:foldl(fun(A, {Acc, #crun_state{temp_vars=TempVars}=State_1}) -> + Save_0 = etp_get_temp_var(0, TempVars), + Save_1 = etp_get_temp_var(1, TempVars), + State_1_1 = etp_store_temp(0, A, etp_store_temp(1, Acc, State_1)), + {Ret_1, State_2} = etp_run(DoEr, State_1_1#crun_state{p=A}, Dict), + State_2_1 = etp_store_temp(0, Save_0, etp_store_temp(1, Save_1, State_2)), + {Ret_1, State_2_1} + end, {none, State}, List_1), + {Ret_2, State_3}; + +etp_run_call("foldr", Args, State_0, Dict) -> + [DoEr, ListArg | _] = Args, + {List_1, State} = etp_run(ListArg, State_0, Dict), + {Ret_2, State_3} = lists:foldr(fun(A, {Acc, #crun_state{temp_vars=TempVars}=State_1}) -> + Save_0 = etp_get_temp_var(0, TempVars), + Save_1 = etp_get_temp_var(1, TempVars), + State_1_1 = etp_store_temp(0, A, etp_store_temp(1, Acc, State_1)), + {Ret_1, State_2} = etp_run(DoEr, State_1_1#crun_state{p=A}, Dict), + State_2_1 = etp_store_temp(0, Save_0, etp_store_temp(1, Save_1, State_2)), + {Ret_1, State_2_1} + end, {none, State}, List_1), + {Ret_2, State_3}. + +etp_store_temp(K, V, #crun_state{temp_vars=TempVars}=State) -> + State#crun_state{temp_vars=TempVars#{K=>V}}. + +etp_get_temp_var(Key, TempVars) when is_map(TempVars) -> + case maps:find(Key, TempVars) of + {ok, Val} -> Val; + _ -> none + end. + + +%% Easier access to some record fields than by tuple index +tuple_n_from_field(Tuple, FieldName) + when is_tuple(Tuple) -> + TT = element(1, Tuple), + tuple_n_from_f(TT, FieldName). +tuple_n_from_f(st, F) -> + tuple_n_from_f(st, F, 1, [ + "shapes", + "selmode", + "sh", + "sel", + "ssels", + "temp_sel", + "mat", + "pal", + "file", + "saved", + "onext", + "bb", + "edge_loop", + "views", + "pst", + "repeatable", + "ask_args", + "drag_args", + "def", + "last_cmd", + "undo", + "next_is_undo", + "undone" + ]); +tuple_n_from_f(we, F) -> + tuple_n_from_f(we, F, 1, [ + "id", + "perm", + "name", + "es", + "lv", + "rv", + "fs", + "he", + "vc", + "vp", + "pst", + "mat", + "next_id", + "mirror", + "light", + "holes", + "temp" + ]); +tuple_n_from_f(edge, F) -> + tuple_n_from_f(edge, F, 1, [ + "vs", + "ve", + "lf", + "rf", + "ltpr", + "ltsu", + "rtpr", + "rtsu" + ]); +tuple_n_from_f(view, F) -> + tuple_n_from_f(view, F, 1, [ + "origin", + "distance", + "azimuth", + "elevation", + "pan_x", + "pan_y", + "along_axis", + "fov", + "hither", + "yon" + ]); +tuple_n_from_f(dlo, F) -> + tuple_n_from_f(dlo, F, 1, [ + "work", + "smooth", + "edges", + "vs", + "hard", + "sel", + "orig_sel", + "normals", + "vab", + "tri_map", + "hilite", + "mirror", + "ns", + "plugins", + "proxy", + "proxy_data", + "src_we", + "src_sel", + "split", + "drag", + "transparent", + "open", + "needed" + ]); +tuple_n_from_f(vab, F) -> + tuple_n_from_f(vab, F, 1, [ + "id", + "data", + "face_vs", + "face_fn", + "face_sn", + "face_uv", + "face_ts", + "face_vc", + "face_es", + "face_map", + "mat_map" + ]); +tuple_n_from_f(e3d_transf, F) -> + tuple_n_from_f(e3d_transf, F, 1, [ + "mat", + "inv" + ]); +tuple_n_from_f(ray, F) -> + tuple_n_from_f(ray, F, 1, [ + "o", + "d", + "n", + "f", + "bfc" + ]); +tuple_n_from_f(e3d_face, F) -> + tuple_n_from_f(e3d_face, F, 1, [ + "vs", + "vc", + "tx", + "ns", + "mat", + "sg", + "vis" + ]); +tuple_n_from_f(e3d_mesh, F) -> + tuple_n_from_f(e3d_mesh, F, 1, [ + "type", + "vs", + "vc", + "tx", + "ns", + "fs", + "he", + "matrix" + ]); +tuple_n_from_f(e3d_object, F) -> + tuple_n_from_f(e3d_object, F, 1, [ + "name", + "obj", + "mat", + "attr" + ]); +tuple_n_from_f(e3d_file, F) -> + tuple_n_from_f(e3d_file, F, 1, [ + "objs", + "mat", + "creator", + "dir" + ]); +tuple_n_from_f(e3d_image, F) -> + tuple_n_from_f(e3d_image, F, 1, [ + "type", + "bytes_pp", + "alignment", + "order", + "width", + "height", + "image", + "filename", + "name", + "extra" + ]); +tuple_n_from_f(keyboard, F) -> + tuple_n_from_f(keyboard, F, 1, [ + "which", + "state", + "scancode", + "sym", + "mod", + "unicode" + ]); +tuple_n_from_f(mousemotion, F) -> + tuple_n_from_f(mousemotion, F, 1, [ + "which", + "state", + "mod", + "x", + "y", + "xrel", + "yrel" + ]); +tuple_n_from_f(mousebutton, F) -> + tuple_n_from_f(mousebutton, F, 1, [ + "which", + "button", + "state", + "mod", + "x", + "y" + ]); +tuple_n_from_f(mousewheel, F) -> + tuple_n_from_f(mousewheel, F, 1, [ + "which", + "dir", + "wheel", + "mod", + "x", + "y" + ]); +tuple_n_from_f(io, F) -> + tuple_n_from_f(io, F, 1, [ + "grab_stack", + "key_up" + ]); +tuple_n_from_f(_, _) -> error. + +tuple_n_from_f(_, _, _, []) -> error; +tuple_n_from_f(_, F, I, [F | _]) -> {ok, I}; +tuple_n_from_f(TN, F, I, [_ | R]) -> + tuple_n_from_f(TN, F, I+1, R). + + +liststr_to_binstring(A) -> + binstr(A). + +binstr(Str) when is_list(Str) -> + unicode:characters_to_nfc_binary(Str); +binstr(Str) when is_binary(Str) -> + Str. + +unbinstr(Bin) when is_binary(Bin) -> + unicode:characters_to_list(Bin, utf8). + + +%% Starting parses need to begin with something +etp_start_parse(A) -> + {Thing, R} = etp_next_thing(A), + etp_parse(R, [{start, Thing}]). + +etp_parse([], Cont) -> + {lists:reverse(Cont), []}; +etp_parse([close_b | R], Cont) -> + {lists:reverse(Cont), R}; +etp_parse([close_c | R], Cont) -> + {lists:reverse(Cont), R}; +etp_parse([close_p | R], Cont) -> + {lists:reverse(Cont), R}; +etp_parse([cma | R], Cont) -> + {cma, lists:reverse(Cont), R}; + +etp_parse([open_c | R], Cont) -> + {Cont_1, R_1} = etp_start_parse(R), + etp_parse(R_1, [{tuple_nth, Cont_1} | Cont]); +etp_parse([open_b | R], Cont) -> + {Cont_1, R_1} = etp_start_parse(R), + etp_parse(R_1, [{list_nth, Cont_1} | Cont]); +etp_parse([slash | R], Cont) -> + {Cont_1, R_1} = etp_next_thing(R), + etp_parse(R_1, [{orddict_f, Cont_1} | Cont]); +etp_parse([dot, {word, Word} | R], Cont) -> + %% Find record field name + etp_parse(R, [{rec_f, Word} | Cont]); + + + +etp_parse([store_to, {integer, N} | R], Cont) -> + etp_parse(R, [{store_to, N} | Cont]); + +etp_parse([store_to, {atom, A} | R], Cont) -> + etp_parse(R, [{store_to, binstr(A)} | Cont]); + +etp_parse([store_to, {string, S} | R], Cont) -> + etp_parse(R, [{store_to, binstr(S)} | Cont]). + +etp_list_parse(A) -> + etp_list_parse(A, []). +etp_list_parse(A, O) -> + case etp_start_parse(A) of + {cma, Cont, R} -> + etp_list_parse(R, [Cont | O]); + {Cont, R} -> + {lists:reverse([Cont | O]), R} + end. + +etp_next_thing([{word, Mod}, col, {word, W}, open_p | R]) -> + {List, R_1} = etp_list_parse(R), + {{call, Mod, W, List}, R_1}; +etp_next_thing([{word, W}, open_p | R]) -> + {List, R_1} = etp_list_parse(R), + {{call, W, List}, R_1}; +etp_next_thing([{float, W} | R]) -> + {{float, W}, R}; +etp_next_thing([{integer, W} | R]) -> + {{integer, W}, R}; +etp_next_thing([{atom, W} | R]) -> + {{atom, W}, R}; +etp_next_thing([{string, W} | R]) -> + {{string, W}, R}; +etp_next_thing([{word, StartWord} | R]) -> + {{word, StartWord}, R}; +etp_next_thing([get_from, {integer, N} | R]) -> + {{get_from, N}, R}; +etp_next_thing([get_from, {atom, A} | R]) -> + {{get_from, binstr(A)}, R}; +etp_next_thing([get_from, {string, N} | R]) -> + {{get_from, binstr(N)}, R}. + +etp_tok([], [], O) -> + lists:reverse(O); +etp_tok([], WO, O) -> + etp_tok([], [], [etp_word_or_number(WO) | O]); +etp_tok([$. | Rest], [Alph | _] = WO, O) + when Alph >= $a andalso Alph =< $z -> + %% Tuple record find + etp_tok(Rest, [], [dot, etp_word_or_number(WO) | O]); +etp_tok([$. | Rest], [], O) -> + %% Tuple record find + etp_tok(Rest, [], [dot | O]); +etp_tok([Digit | Rest], [], [dot | O]) + when Digit >= $0 andalso Digit =< $9 -> + %% Revert dot from tuple record to number + etp_tok(Rest, [Digit,$.], O); +etp_tok([$/ | Rest], [], O) -> + %% Assoc list find + etp_tok(Rest, [], [slash | O]); +etp_tok([$( | Rest], [], O) -> + %% Parenthesis + etp_tok(Rest, [], [open_p | O]); +etp_tok([$) | Rest], [], O) -> + %% Closing parenthesis + etp_tok(Rest, [], [close_p | O]); +etp_tok([${ | Rest], [], O) -> + %% Tuple nth + etp_tok(Rest, [], [open_c | O]); +etp_tok([$} | Rest], [], O) -> + %% Closing tuple nth + etp_tok(Rest, [], [close_c | O]); +etp_tok([$[ | Rest], [], O) -> + %% List nth + etp_tok(Rest, [], [open_b | O]); +etp_tok([$] | Rest], [], O) -> + %% Closing list nth + etp_tok(Rest, [], [close_b | O]); +%etp_tok([$$ | Rest], [], O) -> + %% Error +% {error, syntax}; +etp_tok([$>, $$ | Rest], [], O) -> + %% Store value + %% Get number, atom or string + etp_tok(Rest, [], [store_to | O]); +etp_tok([$<, $$ | Rest], [], O) -> + %% Fetch value + %% Get number, atom or string + etp_tok(Rest, [], [get_from | O]); +etp_tok([$' | Rest], [], O) -> + %% Atom + %% Get until end of ' + {Atom, Rest_1} = etp_read_atom_in_q(Rest), + etp_tok(Rest_1, [], [{atom, Atom} | O]); + +etp_tok([34 | Rest], [], O) -> + %% String + %% Get until end of double quote + {Str, Rest_1} = etp_read_string_in_dq(Rest), + etp_tok(Rest_1, [], [{string, Str} | O]); + +etp_tok([$: | Rest], [], O) -> + %% Colon + etp_tok(Rest, [], [col | O]); +etp_tok([$, | Rest], [], O) -> + %% Separator + etp_tok(Rest, [], [cma | O]); + +etp_tok([X | _]=Str, WO, O) + when length(WO) > 0, + (X =:= $/ orelse + X =:= ${ orelse X =:= $} orelse + X =:= $[ orelse X =:= $] orelse + X =:= $( orelse X =:= $) orelse + X =:= $> orelse X =:= $< orelse + X =:= $, orelse X =:= $' orelse X =:= 34 orelse X =:= $: ) -> + etp_tok(Str, [], [etp_word_or_number(WO) | O]); + +etp_tok([A | Rest], WO, O) -> + etp_tok(Rest, [A | WO], O). + +etp_word_or_number(WO) -> + A = lists:reverse(WO), + case string:to_float(A) of + {Num_F, _} when is_float(Num_F) -> {float, Num_F}; + _ -> + case string:to_integer(A) of + {Num_I, _} when is_integer(Num_I) -> {integer, Num_I}; + _ -> {word, A} + end + end. + +%% Read the contents of a string +%% +etp_read_string_in_dq(A) -> etp_read_string_in_dq(A, []). +etp_read_string_in_dq([], AL) -> {binstr(lists:reverse(AL)), []}; +etp_read_string_in_dq([BS, EscChar | Rest], AL) + when BS =:= 92 -> %% Backslash + etp_read_string_in_dq(Rest, [EscChar|AL]); +etp_read_string_in_dq([DQ | Rest], AL) + when DQ =:= 34 -> %% Double quote + {binstr(lists:reverse(AL)), Rest}; +etp_read_string_in_dq([Char | Rest], AL) -> + etp_read_string_in_dq(Rest, [Char|AL]). + +%% Read the contents of an atom +%% +etp_read_atom_in_q(A) -> etp_read_atom_in_q(A, []). +etp_read_atom_in_q([], AL) -> {binstr(lists:reverse(AL)), []}; +etp_read_atom_in_q([BS, EscChar | Rest], AL) + when BS =:= 92 -> %% Back slash + etp_read_atom_in_q(Rest, [EscChar|AL]); +etp_read_atom_in_q([AQ | Rest], AL) + when AQ =:= $' -> %% Single quote + {binstr(lists:reverse(AL)), Rest}; +etp_read_atom_in_q([Char | Rest], AL) -> + etp_read_atom_in_q(Rest, [Char|AL]). + + + +warn(Str) -> + io:format("~p: WARNING: ~s~n", [?MODULE, Str]). + +scr_debug(Str) -> + io:format("DEBUG: ~s~n", [Str]). + +err(Str) -> + io:format("~p: ERROR: ~s~n", [?MODULE,Str]). + + + +-ifdef(TEST). +test_write_scm() -> + binstr(write_scm([{atom, <<"test">>}, {string, <<"string">>}, false, [1,2,3,4], [5,6,7,8]])). +test_scm_parse() -> + scm_parse(<<"(test '(0 1 2 3) #(2 3 4) '(\"Test\" 2 #f))">>). +test_wscr_content() -> + read_wscr_content( + <<"type \"py\"\n" + "name \"My Example Script\"\n", + "params {\n" + " param \"Top\" \"top\"\n" + " param \"Bottom\" \"bottom\"\n" + " param \"Sides\" \"sides\"\n" + "}\n">>, []). + +test() -> + Dict = [{"st", {st, 1,2,3,4}}], + L = + [["ifdef", {etp,"st{3}/plugins/wpc_example{3}"}, []], + ["if", {etp,"st{2}/plugins/wpc_example{3}"}, "eq", {etp,"0"}, []], + ["if", {etp,"st{1}/plugins/wpc_example{3}"}, "gt", {etp,"0"}, []], + ["script_param", "paramname", {etp,"st.shapes"}], + ["script_param", "paramname2", {etp,"tuple(1,2,3){0}"}], + ["script_param", "paramname3", {etp,"lists:nth(1,list(1,2,3,4))>$'test1'"], + ["script_param", "paramname4", {etp,"<$'test1'"}], + ["import_param", "key", {etp,"\"val\""}], + ["export_param", "tesselation", {atom,"triangulation"], + ["save_pref", "k", {atom,"v"}], + ["load_pref", "k", "v", {atom,"v"}]], + crun_list(L, #crun_state{}, Dict). + +t() -> + Strings = #{ + "en" => [ + {{plugin,name}, "My Plugin"}, + {{menu,newsubmenu}, "New Submenu"}, + {{menu,newnewsubmenu}, "New new Submenu"}, + {{menu,newnewsubmenu2}, "New new Submenu"}, + {{menu,myplugin}, "My Plugin"}, + {{info,myplugin}, "My Plugin menu tooltip"}, + {{menu,myplugin2}, "My Plugin"}, + {{info,myplugin2}, "My Plugin menu tooltip"} + ]}, + + lists:usort(lists:append([ + pluginscripts_menus([file,newsubmenu,newnewsubmenu,myplugin],{[],'test',Strings}), + pluginscripts_menus([file,newsubmenu,newnewsubmenu,myplugin2],{[],'test',Strings}), + pluginscripts_menus([file,newsubmenu,newnewsubmenu2,myplugin],{[],'test',Strings}), + pluginscripts_menus([file,newsubmenu,newnewsubmenu2,myplugin2],{[],'test',Strings}) + ])). + +t3() -> + {L0,L3}=get_pluginscripts(["pluginscripts"]), + L = get_pluginscripts_1(L3, L0), + io:format("file -> ~p~n", [pluginscripts_make_menu({file}, [], L)]), + io:format("tools -> ~p~n", [pluginscripts_make_menu({tools}, [], L)]), + io:format("tools/newsubmenu -> ~p~n", [pluginscripts_make_menu({tools,newsubmenu}, [], L)]), + io:format("tools/newsubmenu/newnewsubmenu -> ~p~n", [pluginscripts_make_menu({tools,newsubmenu,newnewsubmenu}, [], L)]). + +t2() -> + {L0,L3}=get_scriptfolders(["scriptfolders"]), + L = get_pluginscripts_1(L3, L0), + io:format("body -> ~p~n", [pluginscripts_make_menu({body}, [], L)]), + io:format("face -> ~p~n", [pluginscripts_make_menu({face}, [], L)]), + io:format("shape -> ~p~n", [pluginscripts_make_menu({shape}, [], L)]). + +-endif. + + diff --git a/plugins_src/scripting/wpc_scripting_shapes_en.lang b/plugins_src/scripting/wpc_scripting_shapes_en.lang new file mode 100644 index 000000000..8154cead7 --- /dev/null +++ b/plugins_src/scripting/wpc_scripting_shapes_en.lang @@ -0,0 +1,132 @@ +%% -*- mode:erlang; erlang-indent-level: 2 -*- +{wpc_scripting_shapes, + [ + {menu, + [ + {1,"Scripts Preference"} + ]}, + {from_script_menu, + [ + {1,"Shape from Script"}, + {2,"Script-based Importers..."}, + {3,"Script-based Exporters..."}, + {4,"Script-based Commands..."} + ]}, + {help_script_menu, + [ + {1,"Using Scripts"}, + {2,"Scripting Information"}, + {3,"Scripting Reference"} + ]}, + {mouse_choice, + [ + {1,"From Script"} + ]}, + {installing_archive, + [ + {1,"plugin script"} + ]}, + {help_information, + [ + {1,"Scripting Information"}, + {2,"Scripting Information\n\n" + "Scripts are small programs that can be used to create new " + "shapes, importers, exporters and commands. Before " + "scripts can be used, the scripting interpreters needs to be " + "installed and its path set in 'Scripting Preference'.\n\n" + "The two scripting languages that are available are Python " + "and Scheme.\n\n" + "Each script needs the script itself (.py or .scm) and " + "a .wscr file with the same name before the extension for " + "the script to work."} + ]}, + {run_script_w_preview, + [ + {1, "Script file not found, check if script has the same name " + "as the .wscr file, and 'type' matches script file type."} + ]}, + {run_script_w_preview_1, + [ + {2,"Returned: "} + ]}, + {run_script_w_preview_1_fun, + [ + {1,"Did not recv 'exited'"} + ]}, + {run_script_1, + [ + {1,"Tuple:"}, + {2,"No results"} + ]}, + {info_dialog, + [ + {1,"Info"} + ]}, + {run_script_runner, + [ + {1,"could not run script interpreter:"} + ]}, + {run_script_runner_loop, + [ + {1,"~p: Unexpected ret: ~p~n"} + ]}, + {run_script_runner_inner_loop, + [ + {1,"NOTE: Script runtime exited with code:"}, + {2,"NOTE: Closing script due to timeout"} + ]}, + {dlg_select_script_do_init, + [ + {1,"Select Script"}, + {2,"Refresh"}, + {3,"OK"}, + {4,"Cancel"} + ]}, + {dlg_script_preference, + [ + {1,"Script Preference"}, + {2,"Enable Scripts"}, + {3,"Shapes"}, + {4,"Commands"}, + {5,"Import/Export"}, + {6,"Interpreters"}, + {7,"Debug"} + ]}, + {dlg_script_preference_paths, + [ + {4,"Enable Debug Return Data"}, + {9,"Each script must have with it a file with the extension .wscr " + "that contains its name, description and parameters."} + ]}, + {dlg_script_preference_path_item, + [ + {1,"Paths for Shape Scripts"}, + {2,"Paths for Commands Scripts"}, + {3,"Enable Scripts for Commands Menu"}, + {4,"Paths for Import/Export Scripts"}, + {5,"Enable Scripts for Import Menu"}, + {6,"Enable Scripts for Export Menu"}, + {8,"% Interpreter Path"}, + {9,"% Extra Interpreter Arguments"} + ]}, + {table_labels, + [ + {1,"Paths"}, + {2,"Add"}, + {3,"Edit"}, + {4,"Del"} + ]}, + {get_wscr_params_default_param_title, + [ + {1,"Set Parameters"} + ]}, + {menu_script_folder, + [ + {1,"Open Script Folder"} + ]}, + {menu_script_folder_1, + [ + {1,"No scripts available"}, + {2,"None"} + ]} + ]}. diff --git a/src/wings_dialog.erl b/src/wings_dialog.erl index bace5fa86..989d66d32 100644 --- a/src/wings_dialog.erl +++ b/src/wings_dialog.erl @@ -18,7 +18,8 @@ info/3, ask/3, ask/4, ask/5, dialog/3, dialog/4, dialog/5, - ask_preview/5, dialog_preview/5 + ask_preview/5, dialog_preview/5, + manual/1,manual/2 ]). %% Hook callbacks @@ -371,7 +372,24 @@ set_value_impl(#in{wx=Ctrl, type=radiobox, data=Keys}, Val, _) -> Idx = get_list_index(Val,Keys), wxRadioBox:setSelection(Ctrl, Idx); set_value_impl(#in{wx=Ctrl, type=checkbox}, Val, _) -> - wxCheckBox:setValue(Ctrl, Val). + wxCheckBox:setValue(Ctrl, Val); +set_value_impl(In=#in{wx=Ctrl, type=table}, {Sel,Rows}, Store) -> + ColIdx = lists:seq(0, wxListCtrl:getColumnCount(Ctrl)-1), + Li = wxListItem:new(), + Header = + lists:foldr(fun(C, Acc) -> + wxListItem:setMask(Li, ?wxLIST_MASK_TEXT bor ?wxLIST_MASK_WIDTH bor ?wxLIST_MASK_FORMAT), + wxListCtrl:getColumn(Ctrl,C,Li), + [{wxListItem:getText(Li), + wxListItem:getWidth(Li), + wxListItem:getAlign(Li)}|Acc] + end, [], ColIdx), + wxListItem:destroy(Li), + wxListCtrl:clearAll(Ctrl), + fill_table(Ctrl,Header,Rows), + [wxListCtrl:setItemState(Ctrl,N,?wxLIST_STATE_SELECTED,?wxLIST_STATE_SELECTED) || N <- Sel], + wxListCtrl:refresh(Ctrl), + true = ets:insert(Store, In#in{def=Rows}). get_list_index(Val, List) -> get_list_index_0(Val, List, -1). @@ -796,7 +814,7 @@ setup_hook(#in{key=Key, wx=Ctrl, type=dirpicker, hook=UserHook}, Fields) -> end}]), UserHook(Key, wxDirPickerCtrl:getPath(Ctrl), Fields); setup_hook(#in{key=Key, wx=Ctrl, type=table, hook=UserHook}, Fields) -> - wxListCtrl:connect(Ctrl, command_list_item_focused, + wxListCtrl:connect(Ctrl, command_list_item_selected, % command_list_item_focused, [{callback, fun(_, _) -> UserHook(Key, Ctrl, Fields) end}]), @@ -1340,29 +1358,16 @@ build(Ask, {table, [Header|Rows], Flags}, Parent, Sizer, In) -> single -> ?wxLC_SINGLE_SEL bor ?wxLC_REPORT; _ -> ?wxLC_REPORT end, - Options = [{style, Style}, {size, {-1, Height}}], + Options = [{style, Style bor ?wxLC_NO_SORT_HEADER}, {size, {-1, Height}}], Ctrl = wxListCtrl:new(Parent, Options), - {CW, _, _, _} = wxWindow:getTextExtent(Ctrl, "D"), - Li = wxListItem:new(), - AddHeader = fun({HeadStr,W}, Column) -> - wxListItem:setText(Li, HeadStr), - wxListItem:setAlign(Li, ?wxLIST_FORMAT_RIGHT), - wxListCtrl:insertColumn(Ctrl, Column, Li), - wxListCtrl:setColumnWidth(Ctrl, Column, W*CW+10), - Column + 1 - end, - lists:foldl(AddHeader, 0, lists:zip(tuple_to_list(Header), Widths)), - wxListItem:destroy(Li), - - Add = fun({_, Str}, {Row, Column}) -> - wxListCtrl:setItem(Ctrl, Row, Column, Str), - {Row, Column+1} - end, - lists:foldl(fun(Row, N) -> - wxListCtrl:insertItem(Ctrl, N, ""), - lists:foldl(Add, {N, 0}, tuple_to_list(Row)), - N + 1 - end, 0, Rows), + {CW, _, _, _} = wxWindow:getTextExtent(Ctrl, "D"), + + Widths0 = [W*CW+10 || W <- Widths], + Header0 = tuple_to_list(Header), + Align = [?wxLIST_FORMAT_RIGHT || _ <- Header0], + Header1 = lists:zip3(Header0, Widths0, Align), + fill_table(Ctrl,Header1,Rows), + add_sizer(table, Sizer, Ctrl, Flags), Ctrl end, @@ -1732,6 +1737,31 @@ pos(C, [C|_Cs], I) -> I; pos(C, [_|Cs], I) -> pos(C, Cs, I+1); pos(_, [], _I) -> 0. +fill_table(Ctrl,Header,Rows) -> + Li = wxListItem:new(), + AddHeader = fun({HeadStr,W,A}, Column) -> + wxListItem:setText(Li, HeadStr), + wxListItem:setAlign(Li, A), + wxListCtrl:insertColumn(Ctrl, Column, Li), + wxListCtrl:setColumnWidth(Ctrl, Column, W), + Column + 1 + end, + lists:foldl(AddHeader, 0, Header), + wxListItem:destroy(Li), + Add = fun({_, Str}, {Row, Column}) -> + wxListCtrl:setItem(Ctrl, Row, Column, Str), + {Row, Column+1} + end, + lists:foldl(fun(Row0, N) -> + case tuple_to_list(Row0) of + [] -> N; + Row -> + wxListCtrl:insertItem(Ctrl, N, ""), + lists:foldl(Add, {N, 0}, Row), + N + 1 + end + end, 0, Rows). + image_to_bitmap(ImageOrFile) -> Img = case ImageOrFile of File when is_list(File) -> @@ -2090,3 +2120,435 @@ text_wheel_move(Value, #wxMouse{wheelRotation=Count,wheelDelta=Delta}=EvMouse, T ValPercent = Percent + (Count/Delta)*Incr, FromSlider(ValPercent). + +%% Show a manual table of contents +%% +%% manual(Filename, Ps) +%% manual(Fun, Ps) +%% +%% If manual is called with a string, a manual is opened in the file format +%% documented below. If manual is called with a function then the function +%% handles its own custom file format. +%% +%% Example usage: +%% +%% Fun = fun +%% (title) -> +%% %% Return string of the title of the table of contents +%% "Table of contents"; +%% (contents) -> +%% %% The table of contents, leaf nodes are tuples with the title +%% %% of the article and the reference number, which will be +%% %% the number used when this function is called with +%% %% {read,Number}. +%% [{"Chapter",[ +%% {"Article",100}, +%% {"Another Article",101} +%% ]}]; +%% ({read,Number}) -> +%% %% Return the title and contents of an article, +%% %% in the same format as for info/1. +%% {"Title",["Contents of article"]}; +%% end +%% +%% The file format of the manual file: +%% +%% File consists of 512-byte chunks, network byte order. +%% The file is structured as: +%% +%% 0 +---------------+ +%% | Header | +%% 512 +---------------+ +%% | Reflist | +%% 512 +---------------+ +%% | ... | +%% Content offset +---------------+ +%% | Contents | +%% +---------------+ +%% | ... | +%% +---------------+ +%% +%% +%% the first 512 byte chunk contains the header: +%% +%% 6 bytes: "MANUAL" +%% 2 bytes: Version (byte 1: 0x0 byte 2: 0x1) +%% 7 bytes: zero (0x00 ...) +%% 1 byte: length of title without null padding +%% 128 bytes: title (null padded to a length of 127) +%% 1 byte: length of edition date without null padding +%% 31 bytes: edition date (null padded to a length of 31) +%% 256 byte: meta data reference pairs of 8 bytes, each +%% pair contains a 4 byte ID and 4 byte reference +%% number. +%% 4 bytes: Offset to contents +%% 4 bytes: Number of references in reflist +%% 4 bytes: Adler32 checksum of reflist contents +%% 4 bytes: zeros (unused) +%% 4 bytes: File encoding ("utf8") +%% 4 bytes: zeros (unused) +%% 8 bytes: Language locale (space padded) +%% +%% The next one or more 512-byte chunks contains the reflist, +%% which consists of 12-byte triples of: +%% * 4 bytes: the reference number of the resource. +%% * 4 bytes: the offset of the first chunk in 512-byte steps +%% * 4 bytes: the size of the content +%% +%% After the reflist the remaining 512-byte chunks are content +%% chunks that starts at offset to contents, to access a specific +%% content, look up the reference number in the reflist and seek +%% to the 512-byte boundary offset: +%% +%% 4 bytes: size of compressed content +%% variable length: zlib compressed data stream. +%% +%% Some reference numbers have special meanings and contain special +%% data, article content should start at 100 for their reference +%% numbers. +%% +%% Reference numbers: +%% +%% 0: Table of contents +%% 1: Table of MIME Types for each article +%% 2: Table of article titles +%% +%% These tables are packed with the following encoding: +%% +%% Tuple: +%% <1 byte: count between 0 and 20> <... list of entries> +%% +%% List of elements: +%% 21 <4 byte: count> <... list of entries> +%% +%% String: +%% 22 <4 byte: count> +%% +%% Integer: +%% 23 <4 byte: integer> +%% +%% +manual(Location) -> + manual(Location, []). +manual(Location, Ps) -> + Size = proplists:get_value(size, Ps, {250, 300}), + manual(Size, Ps, Location). + +manual(Size, _Ps, Filename) + when is_list(Filename) -> + Langs = [wings_pref:get_value(language), "en"], + manual(Size, _Ps, manual_with_file(Filename, Langs)); +manual(Size, _Ps, F) + when is_function(F) -> + Title = F(title), + Parent = wings_dialog:get_dialog_parent(), + WStyle = ?wxCAPTION bor ?wxRESIZE_BORDER bor + ?wxCLOSE_BOX bor ?wxFRAME_FLOAT_ON_PARENT, + Flags = [{size, Size}, {style, WStyle}], + Frame = wxFrame:new(Parent, ?wxID_ANY, Title, Flags), + manual_init_frame(Frame, F), + wxFrame:show(Frame), + keep; +manual(_Size, _Ps, false) -> + wings_u:error_msg(?__(1,"Manual not found")), + keep. + +%% The search path for finding the manual. +%% +manual_search_paths() -> + Dir1 = filename:join([wings_u:basedir(user_data), wings_u:version(), "plugins"]), + lists:append([ + %% Local plugin directory + manual_search_paths_all_dirs(Dir1), + %% Local patch directory + [wings_start:patch_dir()], + %% Main plugin beam directory + manual_search_paths_all_dirs(filename:join(wings_util:lib_dir(wings), "plugins")), + %% Main beam directory + [filename:dirname(code:where_is_file("wings_start.beam"))] + ]). + +manual_search_paths_all_dirs(Dir) -> + manual_search_paths_all_dirs(Dir,2). +manual_search_paths_all_dirs(_Dir,0) -> + false; +manual_search_paths_all_dirs(Dir,Limit) -> + case file:list_dir(Dir) of + {ok, []} -> %% no files in this directory + false; + {ok, Files} -> + L=[manual_search_paths_all_dirs(filename:join(Dir,File),Limit-1) || File <- Files], + [Dir] ++ lists:append([F || F <- L, is_list(F)]); + _ -> + false + end. + + +%% Search for a manual in the prefered language in all search paths, +%% then search for a manual in the alternative language. +%% + +find_manual(_Dir, _Filename1, _Filename2, _InDirs, []) -> + false; +find_manual(Dir, Filename1, Filename2, InDirs, [Lang|Langs1]) -> + case find_manual_1(Dir, Filename1, Filename2, InDirs, Lang) of + false -> + find_manual(Dir, Filename1, Filename2, InDirs, Langs1); + Filename -> + Filename + end. + +find_manual_1(_Dir, _Filename1, _Filename2, [], _Lang) -> + false; +find_manual_1(Dir, Filename1, Filename2, [InDir|Dirs1], Lang) -> + Filename_0 = Filename1 ++ Lang ++ Filename2, + Filename = filename:join(filename:join(InDir,Dir),Filename_0), + case file:read_file_info(Filename) of + {ok,_} -> + Filename; + _ -> + find_manual_1(Dir, Filename1, Filename2, Dirs1, Lang) + end. + +manual_with_file(Filename_0, Langs) -> + Dir = filename:dirname(Filename_0), + Filename_1 = filename:basename(Filename_0), + SearchDirs = manual_search_paths(), + Filename = case string:split(Filename_1,":LANG:") of + [Filen1,Filen2] -> + find_manual(Dir, Filen1, Filen2, SearchDirs, Langs); + [_] -> + Filename_0 + end, + manual_with_file_1(Filename). +manual_with_file_1(Filename) -> + %% Get the table of contents + {Contents,Types,Titles,Info} = manual_file(Filename, fun (FH,Refs,Info1) -> + {Contents1,_} = manual_unpack_contents(ref_get_at(FH,Refs,0)), + {Types1,_} = manual_unpack_contents(ref_get_at(FH,Refs,1)), + {Titles1,_} = manual_unpack_contents(ref_get_at(FH,Refs,2)), + {Contents1,dict:from_list(Types1),dict:from_list(Titles1),Info1} + end), + fun + (title) -> + proplists:get_value(title, Info, ?__(1,"No Title")); + (contents) -> + Contents; + ({read,Number}) -> + {ok, Cont} = read_article_at(Filename,Number), + Title = string:trim(dict:fetch(Number,Titles)), + case dict:fetch(Number,Types) of + "text/plain+html" -> + {Title,[binary_to_list(Cont)]}; + "text/plain" -> + {Title,[binary_to_list(Cont)]}; + _ -> + error + end + end. + + +manual_init_frame(Frame, F) -> + Contents = manual_get_contents(F), + + Win = wxPanel:new(Frame, [{style, wings_frame:get_border()}]), + #{bg:=BG} = wings_frame:get_colors(), + wxPanel:setBackgroundColour(Win, BG), + + BorderSz = wxBoxSizer:new(?wxVERTICAL), + + wxWindow:setSizer(Win, BorderSz), + + Search = wxTextCtrl:new(Win, ?wxID_ANY, [{style, 0}]), + wxBoxSizer:add(BorderSz, Search, + [{flag, ?wxEXPAND}]), + + NodeTree = wxTreeCtrl:new(Win, + [{id, ?wxID_ANY}, {style, ?wxTR_DEFAULT_STYLE bor ?wxTR_HIDE_ROOT}]), + wxBoxSizer:add(BorderSz, NodeTree, + [{flag, ?wxEXPAND}, {proportion, 10}]), + + wxWindow:connect(NodeTree, command_tree_item_activated, [{callback, + fun(#wx{event=#wxTree{item=Item}}, _Obj) -> + case wxTreeCtrl:getItemData(NodeTree, Item) of + "" -> + ok; + Data when is_integer(Data) -> + manual_show_ref(F, Data) + end + end}]), + + wxWindow:connect(Search, command_text_updated, [{callback, + fun(#wx{event=#wxCommand{cmdString=Str}}, _Obj) -> + ref_refresh(NodeTree, Contents, Str) + end}]), + + ref_refresh(NodeTree, Contents, ""), + + keep. + +manual_show_ref(F, Data) -> + case manual_get_content_at(F, Data) of + {Title,Text} -> + wings_dialog:info(Title, Text, []); + error -> + wings_u:error_msg(?__(1,"Could not open")) + end. + + +manual_get_contents(F) -> + F(contents). + +manual_get_content_at(F, Number) -> + F({read,Number}). + +ref_refresh(TList, Contents, SearchStr) -> + wxTreeCtrl:deleteAllItems(TList), + ERoot = wxTreeCtrl:addRoot(TList, "*"), + ref_refresh_fill(ERoot, TList, Contents, SearchStr), + ok. + +ref_refresh_section(ERoot, TList, Name1, SeList1, SearchStr) -> + R1 = ref_adddir(ERoot, TList, Name1), + ref_refresh_fill(R1, TList, SeList1, SearchStr). + +ref_refresh_fill(R1, TList, SeList1, SearchStr) -> + lists:foreach(fun + ({Name2, SeList2}) when is_list(SeList2) -> + case find_search(SearchStr, Name2, SeList2) of + true -> + ref_refresh_section(R1, TList, Name2, SeList2, SearchStr); + false -> + ok + end; + ({Name3, Id}) when is_integer(Id) -> + case find_search(SearchStr, Name3, Id) of + true -> + ref_add(R1, TList, Name3, Id); + false -> + ok + end + end, SeList1). + +find_search("", _Name, _List) -> + true; +find_search(SearchStr, Name, Int) when is_integer(Int) -> + found_str(Name, SearchStr); +find_search(SearchStr, Name3, List) when is_list(List) -> + find_search(SearchStr, [{Name3,[]}|List]). +find_search(SearchStr, [{_,L}|List]) when is_list(L) -> + find_search(SearchStr, L) + orelse find_search(SearchStr, List); +find_search(SearchStr, [{Name,Int}|List]) when is_integer(Int) -> + found_str(Name, SearchStr) + orelse find_search(SearchStr, List); +find_search(_SearchStr, []) -> + false. + +found_str(Str,Search) -> + case string:str(Str, Search) of + 0 -> + false; + _ -> + true + end. + +ref_adddir(ParentNode, TList, Name) -> + Elm = wxTreeCtrl:appendItem(TList, ParentNode, Name), + wxTreeCtrl:ensureVisible(TList, Elm), + Elm. + +ref_add(ParentNode, TList, Name, Id) -> + wxTreeCtrl:appendItem(TList, ParentNode, Name, [{data, Id}]). + + + +-define(BUINT4,32/unsigned-integer). + +%% Get the text contents at the given number. +%% +read_article_at(Filename, Number) -> + manual_file(Filename, fun (FH,Refs,_Ps) -> + Data = ref_get_at(FH,Refs,Number), + {ok, Data} + end). + +manual_file(Filename, F) -> + {ok,FH} = file:open(Filename,[read,binary]), + {ok,{Refs,Info}} = manual_read_header(FH), + Ret = F(FH,Refs,Info), + file:close(FH), + Ret. + + +to_str(Bin, Len) -> + unicode:characters_to_list(binary:part(Bin, {0, Len}), utf8). + +%% Unpack the binary contents into a term, this is only +%% used by the table of content, table of mime types and +%% table of article names. +%% +manual_unpack_contents(<<23,Int:?BUINT4,Rest/binary>>) -> + {Int,Rest}; +manual_unpack_contents(<<22,Count:?BUINT4,Rest/binary>>) -> + Rest_1 = binary:part(Rest,{Count,byte_size(Rest)-Count}), + Str = to_str(Rest, Count), + {Str, Rest_1}; +manual_unpack_contents(<<21,Count:?BUINT4,Rest/binary>>) -> + manual_unpack_contents_1(Count,Rest); +manual_unpack_contents(<>) + when Count < 21 -> + {List,Rest_1} = manual_unpack_contents_1(Count,Rest), + {list_to_tuple(List),Rest_1}. + +manual_unpack_contents_1(Count,Bin) -> + manual_unpack_contents_1(Count,Bin,[]). +manual_unpack_contents_1(0,Bin,OL) -> + {lists:reverse(OL),Bin}; +manual_unpack_contents_1(Count,Bin,OL) -> + {Val,Rest} = manual_unpack_contents(Bin), + manual_unpack_contents_1(Count-1,Rest,[Val|OL]). + +%% Read header of manual file +%% +manual_read_header(FH) -> + case file:pread(FH,0,16) of + {ok,<<"MANUAL",0,1, _,_,_,_,_,_,_,TitleSz>>} -> + {ok,Title_0} = file:pread(FH,16,128), + Title = to_str(Title_0,TitleSz), + {ok,<>} = file:pread(FH,16+128,32), + EditedDate = to_str(EditedDate_0,EditedDateSz), + {ok,_Unused} = file:pread(FH,16+128+32,256), + Info = [ + {title, Title}, + {date, EditedDate} + ], + {ok,<>} = + file:pread(FH,16+128+32+256,16), + {ok,<<_Enc:4/bytes,_Unused_2:4/bytes,_Language:8/bytes>>} = + file:pread(FH,16+128+32+256+16,16), + + {ok,RefChunks} = file:pread(FH,512,First-512), + Refs = dict:from_list(read_refs(RCount,RefChunks)), + {ok,{{Refs,First},Info}} + end. + +read_refs(RCount,Bin) -> + read_refs(RCount,Bin,[]). +read_refs(I,<>, OL) + when I > 0 -> + read_refs(I-1,Rest, [{Number,{Where,Size}}|OL]); +read_refs(0,_, OL) -> + lists:reverse(OL). + +ref_get_at(FH,{Refs,First},Number) -> + {Get_0,Size_0} = dict:fetch(Number, Refs), + Size = Size_0 bsl 9, %% 512 * Size_0 + Get = First + (Get_0 bsl 9), %% 512 * Get_0 + {ok,<>} = file:pread(FH,Get,Size), + zlib:uncompress(binary:part(Getting,{0,Size_2})). + + + + + diff --git a/src/wings_plugin.erl b/src/wings_plugin.erl index b243536db..2c001620e 100644 --- a/src/wings_plugin.erl +++ b/src/wings_plugin.erl @@ -302,17 +302,19 @@ object_name(Prefix, #st{onext=Oid}) -> %%% install(Name) -> - {Type,Dest} = case install_file_type(Name) of + {Type,Dest,AfterInst} = case install_file_type(Name) of beam -> install_beam(Name); tar -> install_tar(Name) end, - io:format("Installed ~w to ~ts~n",[Type, Dest]), + io:format("Installed ~w to ~ts~n",[case Type of {AtomType,_} -> AtomType; _ -> Type end, Dest]), + AfterInst(Dest), case Type of plugin -> - init_dir(plugin_dir()), wings_u:message(?__(1,"The plug-in was successfully installed.")); patch -> - wings_u:message(?__(2,"The patch was successfully installed, please restart wings.")) + wings_u:message(?__(2,"The patch was successfully installed, please restart wings.")); + {_,CustomStr} -> + wings_u:message(io_lib:format(?__(3,"The ~s was successfully installed."),[CustomStr])) end. install_file_type(Name) -> @@ -342,9 +344,9 @@ install_beam(Name) -> {ok,_} -> if Patch -> wings_start:enable_patches(), - {patch, Dest}; + {patch, Dest, after_install_patch()}; true -> - {plugin, Dest} + {plugin, Dest, after_install_plugin()} end; {error,Reason} -> wings_u:error_msg(?__(1,"Install of \"~s\" failed: ~p"), @@ -357,12 +359,13 @@ erl_tar() -> %% Fool dialyzer the spec is wrong for erl_tar:table() in 20.0-20.2 install_tar(Name) -> {ok,Files} = (erl_tar()):table(Name, [compressed]), - Type = install_verify_files(Files, Name), - Dest = case Type of - plugin -> plugin_dir(); - patch -> wings_start:patch_dir() + Type_0 = install_verify_files(Files, Name), + {Type,Dest,AfterInst} = case Type_0 of + plugin -> {Type_0, plugin_dir(), after_install_plugin()}; + patch -> {Type_0, wings_start:patch_dir(), after_install_patch()}; + manifest -> install_manifest(Name) end, - case erl_tar:extract(Name, [compressed,{cwd,Dest}]) of + case erl_tar:extract(Name, [compressed,{cwd,Dest},{files,Files -- ["manifest.xml"]}]) of ok when Type =:= patch -> wings_start:enable_patches(); ok -> ok; @@ -375,7 +378,13 @@ install_tar(Name) -> [filename:basename(Name), file:format_error(Reason)]) end, - {Type,Dest}. + {Type,Dest,AfterInst}. + +after_install_plugin() -> + fun (_) -> init_dir(plugin_dir()) end. + +after_install_patch() -> + fun (_) -> ok end. install_verify_files(Fs, Name) when is_list(Name) -> install_verify_files(Fs, Name, undefined). @@ -391,6 +400,8 @@ install_verify_files([F|Fs], Name, Content) -> false -> case filename:extension(F) of ".beam" -> install_verify_files(Fs, Name, patch); + ".xml" when F =:= "manifest.xml", Content =:= undefined -> + install_verify_files(Fs, Name, manifest); _ -> install_verify_files(Fs, Name, Content) end end; @@ -410,6 +421,71 @@ is_plugin(Name) -> plugin_dir() -> filename:join([wings_u:basedir(user_data), wings_u:version(), "plugins"]). +install_manifest(Name) -> + case erl_tar:extract(Name,[{files,["manifest.xml"]},compressed,memory]) of + {ok,[{_,ContentBin}]} -> + EF = {event_fun, fun manifest_parse/3}, + ES = {event_state, {unknown,[],{[],""}}}, + case xmerl_sax_parser:stream(ContentBin, [EF,ES]) of + {ok,{Type,List,_}, _} -> + install_manifest_1(Type,List); + {_Error, {_,_,Line}, Reason, _ET, _St} -> + wings_u:error_msg(?__(1,"Manifest file in tar has errors at line ~w: ~p"), + [Line,Reason]) + end; + _ -> + wings_u:error_msg(?__(2,"Manifest file in tar isn't available"),[]) + end. +manifest_parse({startElement, _, LName, _, Attrs0}, _Loc, {TypeAtom,List,{Context,_Text}}=State) -> + case LName of + "manifest" -> State; + "type" when Context =:= [] -> + {TypeAtom,List,{[{type,[],[]}],""}}; + Name -> + Attrs = [{AttrName,AttrVal} || {_,_,AttrName,AttrVal} <- Attrs0], + {TypeAtom,[],{[{Name,Attrs,List}|Context],""}} + end; +manifest_parse({endElement, _, LName, _}=_Ev, _Loc, {TypeAtom,List1,{[{Name,Attrs0,List0}|Context],Text}}=State) -> + case LName of + "manifest" -> State; + "type" when Context =:= [] andalso Name =:= type -> + case string:trim(Text) of + "" -> + {unknown,List0,{Context,""}}; + _ -> + {list_to_atom(Text),List0,{Context,""}} + end; + _ -> + Content = case {List1,string:trim(Text)} of + {[],""} -> []; + {[_|_]=List2,_} -> List2; + {[],Text1} -> Text1 + end, + {TypeAtom,[{Name,Attrs0,Content}|List0],{Context,""}} + end; +manifest_parse({characters, Chars}, _Loc, {TypeAtom,List,{Context,_}}) -> + {TypeAtom,List,{Context,Chars}}; +manifest_parse(_Ev, _Loc, State) -> + State. + +install_manifest_1(Type,List) -> + install_manifest_1(?GET(wings_plugins),Type,List). +install_manifest_1([],_Type,_List) -> + wings_u:error_msg(?__(1,"Could not find plugin to install files."),[]); +install_manifest_1([M|Plugins],Type,List) -> + try M:installing_archive(Type,List) of + next -> + install_manifest_1(Plugins,Type,List); + {SubFolder,TypeStr,AfterInstFun} -> + Dest = filename:join([wings_u:basedir(user_data), wings_u:version(), SubFolder]), + {{Type,TypeStr},Dest,AfterInstFun} + catch + _:_ -> + install_manifest_1(Plugins,Type,List) + end. + + + %%% %%% Plug-in manager. %%% diff --git a/src/wings_va.erl b/src/wings_va.erl index 1790fa691..8c47fda4b 100644 --- a/src/wings_va.erl +++ b/src/wings_va.erl @@ -20,7 +20,8 @@ set_edge_color/4, vtx_attrs/2,vtx_attrs/3,attr/2,new_attr/2,average_attrs/1,average_attrs/2, set_vtx_face_uvs/4, - remove/2,remove/3,renumber/2,merge/2,gc/1,any_update/2]). + remove/2,remove/3,renumber/2,merge/2,gc/1,any_update/2, + set_face_attr_vs/4,replace_attr/3]). -include("wings.hrl"). @@ -493,6 +494,18 @@ gc(#we{lv=Lva0,rv=Rva0,es=Etab}=We) -> any_update(#we{lv=Lva,rv=Rva}, #we{lv=Lva,rv=Rva}) -> false; any_update(_, _) -> true. + +%% set_face_attr_vs(Type, Face, NewList, We0) -> We +%% Set new UV or color values to every vertex of a face, in the same +%% order as the list returned by face_attr/3. +%% +-spec set_face_attr_vs(Type, face_num(), [uv_coords()], #we{}) -> #we{} when Type :: 'uv'; + (Type, face_num(), [vertex_color()], #we{}) -> #we{} when Type :: 'color'. +set_face_attr_vs(Type, Face, NewList, We) -> + set_face_attr_vs_1(Type, Face, NewList, We). + + + %%% %%% Local functions. %%% @@ -808,6 +821,37 @@ mix(_, [_|_], none) -> none; mix(W, [Col1|UV1], [Col2|UV2]) -> [wings_color:mix(W, Col1, Col2)|wings_color:mix(W, UV1, UV2)]. + +set_face_attr_vs_1(Type, Face, NewList, #we{fs=Ftab}=We0) -> + Edge = gb_trees:get(Face, Ftab), + set_face_attr_vs_1(Type, Face, Edge, Edge, lists:reverse(NewList), We0). +set_face_attr_vs_1(_Type, _, LastEdge, LastEdge, [], We) -> We; +set_face_attr_vs_1(Type, Face, Edge, LastEdge, [Val|NewList], #we{es=Etab}=We0) -> + case array:get(Edge, Etab) of + #edge{lf=Face,ltsu=NextEdge} -> + set_face_attr_vs_1(Type, Face, NextEdge, LastEdge, NewList, + set_face_attr_vs_2(Type, Edge, Face, Val, We0)); + #edge{rf=Face,rtsu=NextEdge} -> + set_face_attr_vs_1(Type, Face, NextEdge, LastEdge, NewList, + set_face_attr_vs_2(Type, Edge, Face, Val, We0)) + end. +set_face_attr_vs_2(Type, Edge, Face, Val, We0) -> + set_edge_attrs(Edge, Face, + replace_attr(Type, Val, edge_attrs(Edge, Face, We0)), + We0). + + +%% Replace the UV or color of an opaque attribute +%% +-spec replace_attr(Type, uv_coords(), all_attributes()) -> all_attributes() when Type :: 'uv'; + (Type, vertex_color(), all_attributes()) -> all_attributes() when Type :: 'color'. +replace_attr(uv, UV, OA) -> + new_attr(attr(color, OA), UV); +replace_attr(color, Color, OA) -> + new_attr(Color, attr(uv, OA)). + + + average(L) -> {A0,B0} = average_1(L, [], []), case {wings_color:average(A0),wings_color:average(B0)} of diff --git a/tools/pack_manual b/tools/pack_manual new file mode 100755 index 000000000..63d83bfc6 --- /dev/null +++ b/tools/pack_manual @@ -0,0 +1,175 @@ +#!/usr/bin/env escript + +%% +%% Compress a bunch of text files into a manual for use +%% from within Wings. +%% +%% Copyright 2025 Edward Blake +%% +%% See the file "license.terms" for information on usage and redistribution +%% of this file, and for a DISCLAIMER OF ALL WARRANTIES. +%% +%% $Id$ +%% + + + +main(Args) -> + Flags = args(Args), + OutputFile = orddict:fetch(out, Flags), + Dir = orddict:fetch(dir, Flags), + pack(Dir, OutputFile). + + +args(List) -> + args(List, []). +args(["--out", Filename|Rest], Flags) -> + args(Rest, orddict:store(out,Filename,Flags)); +args(["--" ++ _=Flag|_], _Flags) -> + io:format("Unknown option: ~p~n",[Flag]); +args([Filename|Rest], Flags) -> + case file:read_file_info(Filename) of + {ok,_} -> + args(Rest, orddict:store(dir,Filename,Flags)) + end; +args([], Flags) -> + Flags. + + +pack(Dir, OutputFile) -> + {ok, Cont_0} = file:consult(filename:join(Dir,"INDEX")), + [{info,Info}|Cont] = Cont_0, + erlang:put(number,100), + erlang:put(files,[]), + erlang:put(types,[]), + erlang:put(titles,[]), + Cont_1 = pack_contents(read_files(Dir,Cont)), + Types = pack_contents(lists:reverse(erlang:get(types))), + Titles = pack_contents(lists:reverse(erlang:get(titles))), + Chunks_0 = [ + {0,{table,compress(Cont_1)}}, %% Table of contents + {1,{table,compress(Types)}}, %% MIME Types + {2,{table,compress(Titles)}} %% Titles + | lists:reverse(erlang:get(files))], + {Refs,Chunks,_} = chunks(Chunks_0), + Header = header(Info, Refs), + file:write_file(OutputFile,[Header,Chunks]), + io:format("OK~n",[]). + +-define(BUINT4,32/unsigned-integer). + +read_files(Dir,[Entry|List]) -> + [read_entry(Dir,Entry)|read_files(Dir,List)]; +read_files(_Dir,[]) -> + []. + +read_entry(Dir,{Name,List}) -> + {Name,read_files(Dir,List)}; +read_entry(Dir,{txt,Name,Filename}) -> + Number = erlang:get(number), + erlang:put(number,Number+1), + Files = erlang:get(files), + Types = erlang:get(types), + Titles = erlang:get(titles), + {ok,Cont} = file:read_file(filename:join(Dir,Filename)), + erlang:put(files,[{Number,{txt,compress(Cont)}}|Files]), + erlang:put(types,[{Number,"text/plain+html"}|Types]), + erlang:put(titles,[{Number,Name}|Titles]), + {Name,Number}. + + +header(Info, Refs) -> + Title = proplists:get_value(title,Info,"No Title"), + {Title_1, TitleSz} = padded(Title, 128), + EditedDate = proplists:get_value(date,Info,""), + {EditedDate_1, EditedDateSz} = padded(EditedDate, 31), + Language = proplists:get_value(language,Info,""), + Language_1 = list_to_binary(string:pad(Language, 8)), + + Comments = proplists:get_value(comments,Info,""), + {Comments_1, _CommentsSz} = padded(Comments, 256), + + RefList = iolist_to_binary(refs_list(Refs)), + RefListC = erlang:adler32(RefList), + {RefChunks, Size} = chunk_1(RefList), + Size_1 = 512+Size*512, + RCount = length(Refs), + {HChunk, _} = chunk_1(iolist_to_binary([ + <<"MANUAL">>, + <<0,1,0,0,0,0,0,0,0,TitleSz>>, + Title_1, + <>,EditedDate_1, + Comments_1, + <>, + <<"utf8",0:?BUINT4>>, + Language_1 + ])), + iolist_to_binary([HChunk|RefChunks]). + +padded(Str, Max) -> + if length(Str) < Max -> + Str_1 = unicode:characters_to_nfc_binary(Str), + StrSz = byte_size(Str_1), + Len = Max-StrSz, + {list_to_binary([Str_1, string:copies([0],Len)]),StrSz} + end. + + +pack_contents(Int) + when is_integer(Int) -> + <<23,Int:?BUINT4>>; +pack_contents([C|_]=Str) + when is_integer(C) -> + Data = unicode:characters_to_nfc_binary(Str), + Count = byte_size(Data), + [<<22,Count:?BUINT4>>,Data]; +pack_contents(Tuple) + when is_tuple(Tuple) -> + List = tuple_to_list(Tuple), + Count = length(List), + Data = [pack_contents(Elem) || Elem <- List], + [<>|Data]; +pack_contents(List) + when is_list(List) -> + Count = length(List), + Data = [pack_contents(Elem) || Elem <- List], + [<<21,Count:?BUINT4>>|Data]. + + +refs_list(Refs) -> + [ + <> + || {Number,{Where,Size}} <- Refs]. + +-record(chunkstate, { + l1 = [], %% List of refs for ref chunk + l2 = [], %% List of chunks + size = 0 +}). + +chunks(List) -> + chunks(List, #chunkstate{}). +chunks([{Number,{Atom,Cont}}|List], #chunkstate{l1=OL,l2=OL2,size=Size}) + when is_atom(Atom) -> + {Chunks,Extra} = chunk_1(Cont), + Ref = {Number,{Size,Extra}}, + chunks(List, #chunkstate{l1=[Ref|OL],l2=[Chunks|OL2],size=Size+Extra}); +chunks([], #chunkstate{l1=OL1,l2=OL2,size=Size}) -> + {lists:reverse(OL1),lists:reverse(OL2),Size}. + +chunk_1(Bin) -> + chunk_1(Bin, []). +chunk_1(<>,OL) -> + chunk_1(Rest,[Bin|OL]); +chunk_1(<<>>,OL) -> + {lists:reverse(OL),length(OL)}; +chunk_1(Bin,OL) -> + Len = 512-byte_size(Bin), + Bin_1 = iolist_to_binary([Bin,string:copies([0],Len)]), + {lists:reverse([Bin_1|OL]),length(OL)+1}. + +compress(Bin) -> + Bin_1 = zlib:compress(Bin), + Size = byte_size(Bin_1), + <>. +