diff --git a/playground/src/jvmMain/kotlin/hep/dataforge/vision/solid/simpleCube.kt b/playground/src/jvmMain/kotlin/hep/dataforge/vision/solid/simpleCube.kt index f4558c23..215e742c 100644 --- a/playground/src/jvmMain/kotlin/hep/dataforge/vision/solid/simpleCube.kt +++ b/playground/src/jvmMain/kotlin/hep/dataforge/vision/solid/simpleCube.kt @@ -17,5 +17,5 @@ fun main() { } } - fragment.makeFile(resourceLocation = ResourceLocation.LOCAL) + fragment.makeFile(resourceLocation = ResourceLocation.SYSTEM) } \ No newline at end of file diff --git a/playground/src/jvmMain/kotlin/hep/dataforge/vision/solid/stars.kt b/playground/src/jvmMain/kotlin/hep/dataforge/vision/solid/stars.kt new file mode 100644 index 00000000..dcfd8bcc --- /dev/null +++ b/playground/src/jvmMain/kotlin/hep/dataforge/vision/solid/stars.kt @@ -0,0 +1,37 @@ +package hep.dataforge.vision.solid + +import hep.dataforge.meta.DFExperimental +import hep.dataforge.vision.ResourceLocation +import hep.dataforge.vision.VisionManager +import hep.dataforge.vision.html.fragment +import hep.dataforge.vision.three.server.makeFile +import hep.dataforge.vision.three.server.solid +import kotlinx.html.h1 +import java.nio.file.Paths +import kotlin.random.Random + +@OptIn(DFExperimental::class) +fun main() { + val random = Random(112233) + val fragment = VisionManager.fragment { + h1 { +"Happy new year!" } + vision { + solid { + repeat(100) { + sphere(5) { + x = random.nextDouble(-300.0, 300.0) + y = random.nextDouble(-300.0, 300.0) + z = random.nextDouble(-300.0, 300.0) + material { + color(random.nextInt()) + specularColor(random.nextInt()) + } + detail = 16 + } + } + } + } + } + + fragment.makeFile(Paths.get("stars.html"), resourceLocation = ResourceLocation.EMBED) +} \ No newline at end of file diff --git a/ui/react/src/main/kotlin/hep/dataforge/vision/react/PropertyEditor.kt b/ui/react/src/main/kotlin/hep/dataforge/vision/react/PropertyEditor.kt index 3eb1eb97..bcc27f68 100644 --- a/ui/react/src/main/kotlin/hep/dataforge/vision/react/PropertyEditor.kt +++ b/ui/react/src/main/kotlin/hep/dataforge/vision/react/PropertyEditor.kt @@ -32,7 +32,7 @@ public external interface PropertyEditorProps : RProps { public var provider: MutableItemProvider /** - * Provide default item (greyed out if used + * Provide default item (greyed out if used) */ public var defaultProvider: ItemProvider? @@ -65,11 +65,12 @@ private val PropertyEditorItem: FunctionalComponent = private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) { var expanded: Boolean by useState { true } - val itemName = useMemo( { props.name ?: Name.EMPTY }, arrayOf(props.name)) - var item: MetaItem? by useState { props.provider.getItem(itemName) } + val itemName = props.name ?: Name.EMPTY val descriptorItem: ItemDescriptor? = useMemo({ props.descriptor?.get(itemName) }, arrayOf(props.descriptor, itemName)) + var item: MetaItem? by useState { props.provider.getItem(itemName) } + if (descriptorItem?.hidden == true) return //fail fast for hidden property var actualItem: MetaItem? by useState { diff --git a/ui/react/src/main/kotlin/hep/dataforge/vision/react/valueChooser.kt b/ui/react/src/main/kotlin/hep/dataforge/vision/react/valueChooser.kt index 68638e25..86af2e4c 100644 --- a/ui/react/src/main/kotlin/hep/dataforge/vision/react/valueChooser.kt +++ b/ui/react/src/main/kotlin/hep/dataforge/vision/react/valueChooser.kt @@ -9,132 +9,156 @@ import hep.dataforge.vision.widgetType import kotlinx.html.InputType import kotlinx.html.js.onChangeFunction import kotlinx.html.js.onKeyDownFunction -import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLInputElement import org.w3c.dom.HTMLSelectElement import org.w3c.dom.events.Event import react.* -import react.dom.defaultValue import react.dom.option import styled.styledInput import styled.styledSelect public external interface ValueChooserProps : RProps { - public var item: MetaItem? + public var item: MetaItem? public var descriptor: ValueDescriptor? public var valueChanged: ((Value?) -> Unit)? } -public external interface ValueChooserState : RState { - public var rawInput: Boolean? -} +@JsExport +public val StringValueChooser: FunctionalComponent = + functionalComponent("StringValueChooser") { props -> + var value by useState(props.item.string ?: "") + val keyDown: (Event) -> Unit = { event -> + if (event.type == "keydown" && event.asDynamic().key == "Enter") { + value = (event.target as HTMLInputElement).value + if(value!= props.item.string) { + props.valueChanged?.invoke(value.asValue()) + } + } + } + val handleChange: (Event) -> Unit = { + value = (it.target as HTMLInputElement).value + } + styledInput(type = InputType.text) { + attrs { + this.value = value + onKeyDownFunction = keyDown + onChangeFunction = handleChange + } + } + } @JsExport -public class ValueChooserComponent(props: ValueChooserProps) : RComponent(props) { - private val element = createRef() - - private fun getValue(): Value? { - val element = element.current ?: return null//state.element ?: return null - return when (element) { - is HTMLInputElement -> if (element.type == "checkbox") { - if (element.checked) True else False - } else { - element.value.asValue() +public val BooleanValueChooser: FunctionalComponent = + functionalComponent("BooleanValueChooser") { props -> + var checkedValue by useState(props.item.boolean ?: false) + val handleChange: (Event) -> Unit = { + val newValue = (it.target as HTMLInputElement).checked + checkedValue = newValue + props.valueChanged?.invoke(newValue.asValue()) + } + styledInput(type = InputType.checkBox) { + attrs { + this.attributes["indeterminate"] = (checkedValue == null).toString() + checked = checkedValue + onChangeFunction = handleChange } - is HTMLSelectElement -> element.value.asValue() - else -> error("Unknown event target: $element") } } - private val commit: (Event) -> Unit = { _ -> - props.valueChanged?.invoke(getValue()) - } - - private val keyDown: (Event) -> Unit = { event -> - if (event.type == "keydown" && event.asDynamic().key == "Enter") { - commit(event) +@JsExport +public val NumberValueChooser: FunctionalComponent = + functionalComponent("NumberValueChooser") { props -> + var value by useState(props.item.string ?: "") + val keyDown: (Event) -> Unit = { event -> + if (event.type == "keydown" && event.asDynamic().key == "Enter") { + value = (event.target as HTMLInputElement).value + val number = value.toDoubleOrNull() + if (number == null) { + console.error("The input value $value is not a number") + } else { + props.valueChanged?.invoke(number.asValue()) + } + } + } + val handleChange: (Event) -> Unit = { + value = (it.target as HTMLInputElement).value + } + styledInput(type = InputType.number) { + attrs { + this.value = value + onKeyDownFunction = keyDown + onChangeFunction = handleChange + props.descriptor?.attributes?.get("step").string?.let { + step = it + } + props.descriptor?.attributes?.get("min").string?.let { + min = it + } + props.descriptor?.attributes?.get("max").string?.let { + max = it + } + } } } - override fun shouldComponentUpdate( - nextProps: ValueChooserProps, - nextState: ValueChooserState - ): Boolean = nextProps.item !== props.item - - override fun componentDidUpdate(prevProps: ValueChooserProps, prevState: ValueChooserState, snapshot: Any) { - (element.current as? HTMLInputElement)?.let { element -> - if (element.type == "checkbox") { - element.defaultChecked = props.item?.boolean ?: false - } else { - element.defaultValue = props.item?.string ?: "" +@JsExport +public val ComboValueChooser: FunctionalComponent = + functionalComponent("ComboValueChooser") { props -> + var selected by useState(props.item.string ?: "") + val handleChange: (Event) -> Unit = { + selected = (it.target as HTMLSelectElement).value + props.valueChanged?.invoke(selected.asValue()) + } + styledSelect { + props.descriptor?.allowedValues?.forEach { + option { + +it.string + } + } + attrs { + this.value = props.item?.string ?: "" + multiple = false + onChangeFunction = handleChange } - element.indeterminate = props.item == null } } - private fun RBuilder.stringInput() = styledInput(type = InputType.text) { - attrs { - this.defaultValue = props.item?.string ?: "" - onKeyDownFunction = keyDown +@JsExport +public val ColorValueChooser: FunctionalComponent = + functionalComponent("ColorValueChooser") { props -> + var value by useState( + props.item.value?.let { value -> + if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int) + else value.string + } ?: "#000000" + ) + val handleChange: (Event) -> Unit = { + value = (it.target as HTMLInputElement).value + props.valueChanged?.invoke(value.asValue()) + } + styledInput(type = InputType.color) { + attrs { + this.value = value + onChangeFunction = handleChange + } } - ref = element } - override fun RBuilder.render() { - val descriptor = props.descriptor - val type = descriptor?.type?.firstOrNull() - when { - state.rawInput == true -> stringInput() - descriptor?.widgetType == "color" -> styledInput(type = InputType.color) { - ref = element - attrs { - this.defaultValue = props.item?.value?.let { value -> - if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int) - else value.string - } ?: "#000000" - onChangeFunction = commit - } - } - type == ValueType.BOOLEAN -> { - styledInput(type = InputType.checkBox) { - ref = element - attrs { - defaultChecked = props.item?.boolean ?: false - onChangeFunction = commit - } - } - } - type == ValueType.NUMBER -> styledInput(type = InputType.number) { - ref = element - attrs { - descriptor.attributes["step"].string?.let { - step = it - } - descriptor.attributes["min"].string?.let { - min = it - } - descriptor.attributes["max"].string?.let { - max = it - } - defaultValue = props.item?.string ?: "" - onKeyDownFunction = keyDown - } - } - descriptor?.allowedValues?.isNotEmpty() ?: false -> styledSelect { - descriptor!!.allowedValues.forEach { - option { - +it.string - } - } - ref = element - attrs { - this.value = props.item?.string ?: "" - multiple = false - onChangeFunction = commit - } - } - else -> stringInput() - } +@JsExport +public val ValueChooser: FunctionalComponent = functionalComponent("ValueChooser") { props -> + val rawInput by useState(false) + + val descriptor = props.descriptor + val type = descriptor?.type?.firstOrNull() + + when { + rawInput -> child(StringValueChooser, props) + descriptor?.widgetType == "color" -> child(ColorValueChooser, props) + type == ValueType.BOOLEAN -> child(BooleanValueChooser, props) + type == ValueType.NUMBER -> child(NumberValueChooser, props) + descriptor?.allowedValues?.isNotEmpty() ?: false -> child(ComboValueChooser, props) + //TODO handle lists + else -> child(StringValueChooser, props) } } @@ -142,9 +166,9 @@ internal fun RBuilder.valueChooser( name: Name, item: MetaItem?, descriptor: ValueDescriptor? = null, - callback: (Value?) -> Unit + callback: (Value?) -> Unit, ) { - child(ValueChooserComponent::class) { + child(ValueChooser) { attrs { key = name.toString() this.item = item diff --git a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/Vision.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/Vision.kt index 836d6f26..4e3b65a1 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/Vision.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/Vision.kt @@ -1,5 +1,6 @@ package hep.dataforge.vision +import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.Meta import hep.dataforge.meta.MetaItem import hep.dataforge.meta.MutableItemProvider @@ -73,6 +74,7 @@ public interface Vision : Described { * Flow of property invalidation events. It does not contain property values after invalidation since it is not clear * if it should include inherited properties etc. */ + @DFExperimental @OptIn(ExperimentalCoroutinesApi::class) public val propertyChanges: Flow get() = callbackFlow { diff --git a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/html/VisionTagConsumer.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/html/VisionTagConsumer.kt index 101bef5b..5d0a7be7 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/html/VisionTagConsumer.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/html/VisionTagConsumer.kt @@ -73,7 +73,7 @@ public abstract class VisionTagConsumer( @OptIn(DFExperimental::class) public inline fun TagConsumer.vision( - name: String, + name: String = DEFAULT_VISION_NAME, visionProvider: VisionOutput.() -> Vision, ): T = vision(name.toName(), visionProvider) @@ -103,5 +103,7 @@ public abstract class VisionTagConsumer( public const val OUTPUT_NAME_ATTRIBUTE: String = "data-output-name" public const val OUTPUT_ENDPOINT_ATTRIBUTE: String = "data-output-endpoint" public const val DEFAULT_ENDPOINT: String = "." + + public const val DEFAULT_VISION_NAME = "vision" } } \ No newline at end of file diff --git a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/html/staticHtmlRender.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/html/staticHtmlRender.kt index 1ecbdb8a..5a275e82 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/html/staticHtmlRender.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/html/staticHtmlRender.kt @@ -18,9 +18,10 @@ public fun FlowContent.embedVisionFragment( val consumer = object : VisionTagConsumer(consumer, idPrefix) { override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) { script { + type = "text/json" attributes["class"] = OUTPUT_DATA_CLASS unsafe { - +manager.encodeToString(vision) + +"\n${manager.encodeToString(vision)}\n" } } } diff --git a/visionforge-core/src/jvmMain/kotlin/hep/dataforge/vision/headers.kt b/visionforge-core/src/jvmMain/kotlin/hep/dataforge/vision/headers.kt index 80e5b250..7fe87dbb 100644 --- a/visionforge-core/src/jvmMain/kotlin/hep/dataforge/vision/headers.kt +++ b/visionforge-core/src/jvmMain/kotlin/hep/dataforge/vision/headers.kt @@ -35,7 +35,7 @@ public enum class ResourceLocation { EMBED } -internal const val DATAFORGE_ASSETS_PATH = ".dataforge/assets" +internal const val VISIONFORGE_ASSETS_PATH = ".dataforge/vision/assets" /** @@ -43,14 +43,14 @@ internal const val DATAFORGE_ASSETS_PATH = ".dataforge/assets" * @param */ internal fun checkOrStoreFile(basePath: Path, filePath: Path, resource: String): Path { - val fullPath = basePath.resolveSibling(filePath).toAbsolutePath() + val fullPath = basePath.resolveSibling(filePath).toAbsolutePath().resolve(resource) if (Files.exists(fullPath)) { //TODO checksum } else { //TODO add logging - val bytes = VisionManager::class.java.getResourceAsStream(resource).readAllBytes() + val bytes = VisionManager::class.java.getResourceAsStream("/$resource").readAllBytes() Files.createDirectories(fullPath.parent) Files.write(fullPath, bytes, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE) } @@ -58,7 +58,7 @@ internal fun checkOrStoreFile(basePath: Path, filePath: Path, resource: String): return if (basePath.isAbsolute && fullPath.startsWith(basePath)) { basePath.relativize(fullPath) } else { - filePath + fullPath } } @@ -78,7 +78,7 @@ internal fun embedScriptHeader(resource: String): HtmlFragment = { script { type = "text/javascript" unsafe { - val bytes = VisionManager::class.java.getResourceAsStream(resource).readAllBytes() + val bytes = VisionManager::class.java.getResourceAsStream("/$resource").readAllBytes() +bytes.toString(Charsets.UTF_8) } } @@ -108,12 +108,12 @@ public fun Context.Companion.scriptHeader( val targetPath = when (resourceLocation) { ResourceLocation.LOCAL -> checkOrStoreFile( basePath, - Path.of(DATAFORGE_ASSETS_PATH), + Path.of(VISIONFORGE_ASSETS_PATH), scriptResource ) ResourceLocation.SYSTEM -> checkOrStoreFile( Path.of("."), - Path.of(System.getProperty("user.home")).resolve(DATAFORGE_ASSETS_PATH), + Path.of(System.getProperty("user.home")).resolve(VISIONFORGE_ASSETS_PATH), scriptResource ) ResourceLocation.EMBED -> null diff --git a/visionforge-core/src/jvmMain/kotlin/hep/dataforge/vision/htmlExport.kt b/visionforge-core/src/jvmMain/kotlin/hep/dataforge/vision/htmlExport.kt index f7539a52..6ff99bcb 100644 --- a/visionforge-core/src/jvmMain/kotlin/hep/dataforge/vision/htmlExport.kt +++ b/visionforge-core/src/jvmMain/kotlin/hep/dataforge/vision/htmlExport.kt @@ -20,8 +20,10 @@ public fun HtmlVisionFragment.makeFile( title: String = "VisionForge page", show: Boolean = true, ) { - val actualFile = path ?: Files.createTempFile("tempPlot", ".html") - Files.createDirectories(actualFile.parent) + val actualFile = path?.let { + Path.of(System.getProperty("user.home")).resolve(path) + } ?: Files.createTempFile("tempPlot", ".html") + //Files.createDirectories(actualFile.parent) val htmlString = createHTML().apply { head { meta { diff --git a/visionforge-fx/src/main/kotlin/hep/dataforge/vision/solid/FX3DPlugin.kt b/visionforge-fx/src/main/kotlin/hep/dataforge/vision/solid/FX3DPlugin.kt index 0032f83a..35cd6806 100644 --- a/visionforge-fx/src/main/kotlin/hep/dataforge/vision/solid/FX3DPlugin.kt +++ b/visionforge-fx/src/main/kotlin/hep/dataforge/vision/solid/FX3DPlugin.kt @@ -130,7 +130,7 @@ class FX3DPlugin : AbstractPlugin() { } companion object : PluginFactory { - override val tag = PluginTag("visual.fx3D", PluginTag.DATAFORGE_GROUP) + override val tag = PluginTag("vision.fx3D", PluginTag.DATAFORGE_GROUP) override val type = FX3DPlugin::class override fun invoke(meta: Meta, context: Context) = FX3DPlugin() } diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Solid.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Solid.kt index 61710713..1050dff1 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Solid.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Solid.kt @@ -44,13 +44,13 @@ public interface Solid : Vision { public val Y_POSITION_KEY: Name = POSITION_KEY + Y_KEY public val Z_POSITION_KEY: Name = POSITION_KEY + Z_KEY - public val ROTATION: Name = "rotation".asName() + public val ROTATION_KEY: Name = "rotation".asName() - public val X_ROTATION_KEY: Name = ROTATION + X_KEY - public val Y_ROTATION_KEY: Name = ROTATION + Y_KEY - public val Z_ROTATION_KEY: Name = ROTATION + Z_KEY + public val X_ROTATION_KEY: Name = ROTATION_KEY + X_KEY + public val Y_ROTATION_KEY: Name = ROTATION_KEY + Y_KEY + public val Z_ROTATION_KEY: Name = ROTATION_KEY + Z_KEY - public val ROTATION_ORDER_KEY: Name = ROTATION + "order" + public val ROTATION_ORDER_KEY: Name = ROTATION_KEY + "order" public val SCALE_KEY: Name = "scale".asName() diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidBase.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidBase.kt index b9471724..21c8e5bf 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidBase.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidBase.kt @@ -35,6 +35,6 @@ internal fun Meta.toVector(default: Float = 0f) = Point3D( internal fun Solid.updatePosition(meta: Meta?) { meta[Solid.POSITION_KEY].node?.toVector()?.let { position = it } - meta[Solid.ROTATION].node?.toVector()?.let { rotation = it } + meta[Solid.ROTATION_KEY].node?.toVector()?.let { rotation = it } meta[Solid.SCALE_KEY].node?.toVector(1f)?.let { scale = it } } \ No newline at end of file diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidManager.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidManager.kt index fa67fe5c..e91757a4 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidManager.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidManager.kt @@ -33,7 +33,7 @@ public class SolidManager(meta: Meta) : AbstractPlugin(meta) { } public companion object : PluginFactory { - override val tag: PluginTag = PluginTag(name = "visual.spatial", group = PluginTag.DATAFORGE_GROUP) + override val tag: PluginTag = PluginTag(name = "vision.solid", group = PluginTag.DATAFORGE_GROUP) override val type: KClass = SolidManager::class override fun invoke(meta: Meta, context: Context): SolidManager = SolidManager(meta) diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidMaterial.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidMaterial.kt index 314ea57e..3062b0ad 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidMaterial.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidMaterial.kt @@ -19,12 +19,12 @@ public class SolidMaterial : Scheme() { /** * Primary web-color for the material */ - public var color: ColorAccessor = ColorAccessor(this, COLOR_KEY) + public val color: ColorAccessor = ColorAccessor(this, COLOR_KEY) /** * Specular color for phong material */ - public var specularColor: ColorAccessor = ColorAccessor(this, SPECULAR_COLOR_KEY) + public val specularColor: ColorAccessor = ColorAccessor(this, SPECULAR_COLOR_KEY) /** * Opacity diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/specifications/Axes.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/specifications/Axes.kt index 022fd028..802bebd6 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/specifications/Axes.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/specifications/Axes.kt @@ -1,9 +1,12 @@ package hep.dataforge.vision.solid.specifications -import hep.dataforge.meta.* +import hep.dataforge.meta.Scheme +import hep.dataforge.meta.SchemeSpec +import hep.dataforge.meta.boolean +import hep.dataforge.meta.double public class Axes : Scheme() { - public var visible: Boolean by boolean(rootNode?.isEmpty() != false) + public var visible: Boolean by boolean(false) public var size: Double by double(AXIS_SIZE) public var width: Double by double(AXIS_WIDTH) diff --git a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeCanvas.kt b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeCanvas.kt index a17ab549..d58fba37 100644 --- a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeCanvas.kt +++ b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeCanvas.kt @@ -20,6 +20,7 @@ import info.laht.threekt.external.controls.OrbitControls import info.laht.threekt.external.controls.TrackballControls import info.laht.threekt.geometries.EdgesGeometry import info.laht.threekt.helpers.AxesHelper +import info.laht.threekt.lights.AmbientLight import info.laht.threekt.materials.LineBasicMaterial import info.laht.threekt.math.Vector2 import info.laht.threekt.objects.LineSegments @@ -50,8 +51,11 @@ public class ThreeCanvas( public var axes: AxesHelper = AxesHelper(options.axes.size.toInt()).apply { visible = options.axes.visible } private set + private val light = AmbientLight(0x404040) + private val scene: Scene = Scene().apply { add(axes) + add(light) } public var camera: PerspectiveCamera = buildCamera(options.camera) @@ -66,6 +70,7 @@ public class ThreeCanvas( } private val canvas = (renderer.domElement as HTMLCanvasElement).apply { + className += "three-canvas" width = 600 height = 600 style.apply { @@ -131,7 +136,7 @@ public class ThreeCanvas( } internal fun attach(element: Element) { - check(element.children.length == 0){"The element for Three canvas is not empty"} + check(element.getElementsByClassName("three-canvas").length == 0){"Three canvas already created in this element"} element.appendChild(canvas) updateSize() } diff --git a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeFactory.kt b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeFactory.kt index 30fa1422..9113dbc8 100644 --- a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeFactory.kt +++ b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeFactory.kt @@ -49,7 +49,7 @@ public fun Object3D.updateProperty(source: Vision, propertyName: Name) { updateMaterialProperty(source, propertyName) } else if ( propertyName.startsWith(Solid.POSITION_KEY) - || propertyName.startsWith(Solid.ROTATION) + || propertyName.startsWith(Solid.ROTATION_KEY) || propertyName.startsWith(Solid.SCALE_KEY) ) { //update position of mesh using this object diff --git a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreePlugin.kt b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreePlugin.kt index eaadc56a..080a71f7 100644 --- a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreePlugin.kt +++ b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreePlugin.kt @@ -70,7 +70,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { obj.onPropertyChange(updateScope) { name -> if ( name.startsWith(Solid.POSITION_KEY) || - name.startsWith(Solid.ROTATION) || + name.startsWith(Solid.ROTATION_KEY) || name.startsWith(Solid.SCALE_KEY) ) { //update position of mesh using this object @@ -81,14 +81,6 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { } obj.structureChanges.onEach { (nameToken, _, child) -> -// if (name.isEmpty()) { -// logger.error { "Children change with empty name on $group" } -// return@onChildrenChange -// } - -// val parentName = name.cutLast() -// val childName = name.last()!! - //removing old object findChild(nameToken.asName())?.let { oldChild -> oldChild.parent?.remove(oldChild) @@ -153,7 +145,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { } public companion object : PluginFactory { - override val tag: PluginTag = PluginTag("visual.three", PluginTag.DATAFORGE_GROUP) + override val tag: PluginTag = PluginTag("vision.threejs", PluginTag.DATAFORGE_GROUP) override val type: KClass = ThreePlugin::class override fun invoke(meta: Meta, context: Context): ThreePlugin = ThreePlugin() } diff --git a/visionforge-threejs/visionforge-threejs-server/src/jvmMain/kotlin/hep/dataforge/vision/three/server/serverExtensions.kt b/visionforge-threejs/visionforge-threejs-server/src/jvmMain/kotlin/hep/dataforge/vision/three/server/serverExtensions.kt index a4d84a23..d37fabc3 100644 --- a/visionforge-threejs/visionforge-threejs-server/src/jvmMain/kotlin/hep/dataforge/vision/three/server/serverExtensions.kt +++ b/visionforge-threejs/visionforge-threejs-server/src/jvmMain/kotlin/hep/dataforge/vision/three/server/serverExtensions.kt @@ -39,7 +39,9 @@ public fun HtmlVisionFragment.makeFile( resourceLocation: ResourceLocation = ResourceLocation.SYSTEM, show: Boolean = true, ) { - val actualPath = path ?: Files.createTempFile("tempPlot", ".html") - val scriptHeader = Context.scriptHeader("/js/visionforge-three.js", actualPath, resourceLocation) + val actualPath = path?.let { + Path.of(System.getProperty("user.home")).resolve(path) + } ?: Files.createTempFile("tempPlot", ".html") + val scriptHeader = Context.scriptHeader("js/visionforge-three.js", actualPath, resourceLocation) makeFile(visionManager, path = path, show = show, title = title, headers = arrayOf(scriptHeader)) } \ No newline at end of file