From 53e20fdaae4b9988b7ed40b419780c72cf2906fa Mon Sep 17 00:00:00 2001 From: YTKAB0BP Date: Fri, 15 Nov 2024 03:26:33 +0300 Subject: [PATCH] Time estimation menu --- .../components/bed_menu/SliceMenu.java | 314 +++++++++++++++++- .../slicebeam/fragment/BedFragment.java | 5 + .../slicebeam/slic3r/GCodeViewer.java | 58 ++++ .../ru/ytkab0bp/slicebeam/slic3r/Native.java | 4 + .../ytkab0bp/slicebeam/theme/BeamTheme.java | 22 +- .../ytkab0bp/slicebeam/view/SegmentsView.java | 134 ++++++++ app/src/main/jni/slicebeam/beam_native.cpp | 73 ++++ app/src/main/res/values-ru/strings.xml | 7 +- app/src/main/res/values/attrs.xml | 14 +- app/src/main/res/values/strings.xml | 7 +- 10 files changed, 617 insertions(+), 21 deletions(-) create mode 100644 app/src/main/java/ru/ytkab0bp/slicebeam/view/SegmentsView.java diff --git a/app/src/main/java/ru/ytkab0bp/slicebeam/components/bed_menu/SliceMenu.java b/app/src/main/java/ru/ytkab0bp/slicebeam/components/bed_menu/SliceMenu.java index f82c5b1..eb5996b 100644 --- a/app/src/main/java/ru/ytkab0bp/slicebeam/components/bed_menu/SliceMenu.java +++ b/app/src/main/java/ru/ytkab0bp/slicebeam/components/bed_menu/SliceMenu.java @@ -5,11 +5,13 @@ import static ru.ytkab0bp.slicebeam.utils.DebugUtils.assertTrue; import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.content.res.ColorStateList; import android.net.Uri; import android.os.Build; import android.text.TextUtils; import android.util.TypedValue; import android.view.Gravity; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -22,6 +24,7 @@ import androidx.annotation.NonNull; import androidx.core.content.FileProvider; import androidx.core.util.Pair; +import com.google.android.material.checkbox.MaterialCheckBox; import com.loopj.android.http.AsyncHttpClient; import com.loopj.android.http.AsyncHttpResponseHandler; import com.loopj.android.http.RequestParams; @@ -51,10 +54,13 @@ import ru.ytkab0bp.slicebeam.events.NeedSnackbarEvent; import ru.ytkab0bp.slicebeam.fragment.BedFragment; import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerItem; import ru.ytkab0bp.slicebeam.slic3r.GCodeViewer; +import ru.ytkab0bp.slicebeam.slic3r.Slic3rLocalization; +import ru.ytkab0bp.slicebeam.theme.IThemeView; import ru.ytkab0bp.slicebeam.theme.ThemesRepo; import ru.ytkab0bp.slicebeam.utils.ViewUtils; import ru.ytkab0bp.slicebeam.view.DividerView; import ru.ytkab0bp.slicebeam.view.PositionScrollView; +import ru.ytkab0bp.slicebeam.view.SegmentsView; public class SliceMenu extends ListBedMenu { private AsyncHttpClient client = new AsyncHttpClient(); @@ -71,7 +77,8 @@ public class SliceMenu extends ListBedMenu { protected List onCreateItems(boolean portrait) { lastUid = SliceBeam.CONFIG_UID; List items = new ArrayList<>(Arrays.asList( - new BedMenuItem(R.string.MenuSliceInfo, R.drawable.square_stack_up_outline_28).onClick(v -> fragment.showUnfoldMenu(new LayersMenu(), v)), + new BedMenuItem(R.string.MenuSliceInfo, R.drawable.clock_circle_dashed_outline_24).onClick(v -> fragment.showUnfoldMenu(new InfoMenu(), v)), + new BedMenuItem(R.string.MenuSliceLayers, R.drawable.square_stack_up_outline_28).onClick(v -> fragment.showUnfoldMenu(new LayersMenu(), v)), new BedMenuItem(R.string.MenuSliceExportToFile, R.drawable.folder_simple_arrow_right_outline_28).onClick(v -> { if (fragment.getContext() instanceof Activity) { Activity act = (Activity) fragment.getContext(); @@ -176,15 +183,305 @@ public class SliceMenu extends ListBedMenu { }); } + private final static class InfoMenu extends UnfoldMenu implements IThemeView { + private TextView totalView; + private SegmentsView segmentsView; + private ExtrusionRoleView[] roleViews = new ExtrusionRoleView[GCodeViewer.EXTRUSION_ROLES_COUNT]; + + private GCodeViewer getViewer() { + return fragment.getGlView().getRenderer().getViewer(); + } + + @Override + protected View onCreateView(Context ctx, boolean portrait) { + LinearLayout ll = new LinearLayout(ctx); + ll.setOrientation(LinearLayout.VERTICAL); + + ll.addView(new Space(ctx), new LinearLayout.LayoutParams(0, 0, 1f)); + + for (int i = 0; i < GCodeViewer.EXTRUSION_ROLES_COUNT; i++) { + ll.addView(roleViews[i] = new ExtrusionRoleView(ctx)); + } + + ll.addView(new DividerView(ctx), new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(1f))); + + totalView = new TextView(ctx); + totalView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); + totalView.setGravity(Gravity.CENTER); + ll.addView(totalView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(18)) {{ + topMargin = ViewUtils.dp(8); + }}); + + segmentsView = new SegmentsView(ctx); + ll.addView(segmentsView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(12)) {{ + leftMargin = rightMargin = ViewUtils.dp(12); + topMargin = bottomMargin = ViewUtils.dp(8); + }}); + + ll.addView(new DividerView(ctx), new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(1f))); + LinearLayout toolbar = new LinearLayout(ctx); + toolbar.setPadding(ViewUtils.dp(12), 0, ViewUtils.dp(12), 0); + toolbar.setOrientation(LinearLayout.HORIZONTAL); + toolbar.setGravity(Gravity.CENTER_VERTICAL); + toolbar.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 0)); + toolbar.setOnClickListener(v -> dismiss()); + + ImageView icon = new ImageView(ctx); + icon.setImageResource(R.drawable.arrow_left_outline_28); + icon.setColorFilter(ThemesRepo.getColor(android.R.attr.textColorSecondary)); + toolbar.addView(icon, new LinearLayout.LayoutParams(ViewUtils.dp(28), ViewUtils.dp(28))); + + TextView title = new TextView(ctx); + title.setText(R.string.MenuOrientationPositionBack); + title.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM)); + title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); + title.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary)); + toolbar.addView(title, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f) {{ + leftMargin = ViewUtils.dp(12); + }}); + + ll.addView(toolbar, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(52))); + onApplyTheme(); + return ll; + } + + @Override + protected void onCreate() { + super.onCreate(); + + GCodeViewer viewer = getViewer(); + if (viewer != null) { + for (int i = 0; i < GCodeViewer.EXTRUSION_ROLES_COUNT; i++) { + boolean visible = viewer.getEstimatedTime(i) != 0; + roleViews[i].setVisibility(visible ? View.VISIBLE : View.GONE); + if (visible) { + roleViews[i].bind(viewer, i); + } + } + } + updateValues(); + ViewUtils.postOnMainThread(() -> segmentsView.startAnimation(), 50); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + segmentsView.setNotVisible(); + } + + @Override + public int getRequestedSize(FrameLayout into, boolean portrait) { + if (portrait) { + GCodeViewer viewer = getViewer(); + if (viewer != null) { + int visibleCount = 0; + for (int i = 0; i < GCodeViewer.EXTRUSION_ROLES_COUNT; i++) { + if (viewer.getEstimatedTime(i) != 0) { + visibleCount++; + } + } + return ViewUtils.dp(42) * visibleCount + ViewUtils.dp(28) + ViewUtils.dp(52) + ViewUtils.dp(18 + 8); + } + } + return super.getRequestedSize(into, portrait); + } + + private float getTotalEstimatedTime() { + GCodeViewer viewer = getViewer(); + if (viewer == null) return 0; + float total = 0; + for (int i = 0; i < GCodeViewer.EXTRUSION_ROLES_COUNT; i++) { + if (viewer.isExtrusionRoleVisible(i)) { + total += viewer.getEstimatedTime(i); + } + } + return total; + } + + private void updateValues() { + GCodeViewer viewer = getViewer(); + if (viewer == null) return; + + float[] values = new float[2 + GCodeViewer.EXTRUSION_ROLES_COUNT]; + values[0] = 0; + values[values.length - 1] = 1; + float prev = 0; + int lastVisible = 0; + float total = getTotalEstimatedTime(); + for (int i = 0; i < GCodeViewer.EXTRUSION_ROLES_COUNT; i++) { + if (viewer.isExtrusionRoleVisible(i)) { + float percent = viewer.getEstimatedTime(i) / total; + values[i + 1] = prev + percent; + lastVisible = i; + prev = values[i + 1]; + } else { + values[i + 1] = prev; + } + } + + values[lastVisible] = 1; + + segmentsView.setValues(values); + totalView.setText(formatTime(total)); + } + + private static String formatTime(float time) { + int secondsTotal = Math.round(time); + int seconds = secondsTotal % 60; + int minutes = ((secondsTotal - seconds) / 60) % 60; + int hours = ((secondsTotal - seconds) / 60) / 60; + + StringBuilder sb = new StringBuilder(); + if (hours > 0) { + sb.append(hours).append(" ").append(SliceBeam.INSTANCE.getString(R.string.MenuSliceInfoHour)); + } + if (minutes > 0) { + if (sb.length() > 0) sb.append(" "); + + sb.append(minutes).append(" ").append(SliceBeam.INSTANCE.getString(R.string.MenuSliceInfoMinute)); + } + if (seconds > 0) { + if (sb.length() > 0) sb.append(" "); + + sb.append(seconds).append(" ").append(SliceBeam.INSTANCE.getString(R.string.MenuSliceInfoSecond)); + } + + return sb.toString(); + } + + @Override + public void onApplyTheme() { + totalView.setTextColor(ThemesRepo.getColor(android.R.attr.textColorSecondary)); + } + + private final class ExtrusionRoleView extends LinearLayout implements IThemeView { + private MaterialCheckBox checkBox; + private TextView titleView; + private TextView timeView; + + private Runnable invalidateGl; + + public ExtrusionRoleView(Context context) { + super(context); + setOrientation(HORIZONTAL); + setGravity(Gravity.CENTER_VERTICAL); + setPadding(ViewUtils.dp(12), 0, ViewUtils.dp(16), 0); + + checkBox = new MaterialCheckBox(getContext()) { + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + return false; + } + }; + addView(checkBox, new LinearLayout.LayoutParams(ViewUtils.dp(28), ViewUtils.dp(28))); + + titleView = new TextView(context); + titleView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + addView(titleView, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f) {{ + leftMargin = rightMargin = ViewUtils.dp(12); + }}); + + timeView = new TextView(context); + timeView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); + addView(timeView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(42))); + onApplyTheme(); + } + + public void bind(GCodeViewer viewer, @GCodeViewer.ExtrusionRole int role) { + switch (role) { + case GCodeViewer.EXTRUSION_ROLE_NONE: + titleView.setText(Slic3rLocalization.getString("Unknown")); + break; + case GCodeViewer.EXTRUSION_ROLE_PERIMETER: + titleView.setText(Slic3rLocalization.getString("Perimeter")); + break; + case GCodeViewer.EXTRUSION_ROLE_EXTERNAL_PERIMETER: + titleView.setText(Slic3rLocalization.getString("External perimeter")); + break; + case GCodeViewer.EXTRUSION_ROLE_OVERHANG_PERIMETER: + titleView.setText(Slic3rLocalization.getString("Overhang perimeter")); + break; + case GCodeViewer.EXTRUSION_ROLE_INTERNAL_INFILL: + titleView.setText(Slic3rLocalization.getString("Internal infill")); + break; + case GCodeViewer.EXTRUSION_ROLE_SOLID_INFILL: + titleView.setText(Slic3rLocalization.getString("Solid infill")); + break; + case GCodeViewer.EXTRUSION_ROLE_TOP_SOLID_INFILL: + titleView.setText(Slic3rLocalization.getString("Top solid infill")); + break; + case GCodeViewer.EXTRUSION_ROLE_IRONING: + titleView.setText(Slic3rLocalization.getString("Ironing")); + break; + case GCodeViewer.EXTRUSION_ROLE_BRIDGE_INFILL: + titleView.setText(Slic3rLocalization.getString("Bridge infill")); + break; + case GCodeViewer.EXTRUSION_ROLE_GAP_FILL: + titleView.setText(Slic3rLocalization.getString("Gap fill")); + break; + case GCodeViewer.EXTRUSION_ROLE_SKIRT: + titleView.setText(Slic3rLocalization.getString("Skirt/Brim")); + break; + case GCodeViewer.EXTRUSION_ROLE_SUPPORT_MATERIAL: + titleView.setText(Slic3rLocalization.getString("Support material")); + break; + case GCodeViewer.EXTRUSION_ROLE_SUPPORT_MATERIAL_INTERFACE: + titleView.setText(Slic3rLocalization.getString("Support material interface")); + break; + case GCodeViewer.EXTRUSION_ROLE_WIPE_TOWER: + titleView.setText(Slic3rLocalization.getString("Wipe tower")); + break; + case GCodeViewer.EXTRUSION_ROLE_CUSTOM: + titleView.setText(Slic3rLocalization.getString("Custom")); + break; + } + + timeView.setText(formatTime(viewer.getEstimatedTime(role))); + + checkBox.setChecked(viewer.isExtrusionRoleVisible(role)); + checkBox.setButtonTintList(ColorStateList.valueOf(SegmentsView.mapColor(role))); + setOnClickListener(v -> { + if (getTotalEstimatedTime() == viewer.getEstimatedTime(role)) { + return; + } + + viewer.toggleExtrusionRoleVisible(role); + checkBox.setChecked(!checkBox.isChecked()); + updateValues(); + if (invalidateGl != null) ViewUtils.removeCallbacks(invalidateGl); + ViewUtils.postOnMainThread(invalidateGl = () -> { + Pair p = viewer.getLayersViewRange(); + viewer.setLayersViewRange(p.first, p.second); + fragment.getGlView().requestRender(); + }, 250); + }); + } + + @Override + public void onApplyTheme() { + titleView.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary)); + timeView.setTextColor(ThemesRepo.getColor(android.R.attr.textColorSecondary)); + setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 12)); + } + } + } + private final static class LayersMenu extends UnfoldMenu { private PositionScrollView fromTrack, toTrack; private TextView title; + private Runnable applyCallback; + private GCodeViewer getViewer() { return fragment.getGlView().getRenderer().getViewer(); } private void applyView(int from, int to) { + if (applyCallback != null) ViewUtils.removeCallbacks(applyCallback); + GCodeViewer viewer = getViewer(); if (viewer == null) { return; @@ -218,15 +515,20 @@ public class SliceMenu extends ListBedMenu { if (toTrack.getCurrentPosition() < integer) { toTrack.setCurrentPosition(integer); } - applyView(integer, toTrack.getCurrentPosition()); + title.setText(fragment.getContext().getString(R.string.MenuSliceInfoLayers, fromTrack.getCurrentPosition(), integer)); + + ViewUtils.removeCallbacks(applyCallback); + ViewUtils.postOnMainThread(applyCallback = ()-> applyView(integer, toTrack.getCurrentPosition()), 50); }); fromTrack.setListener(integer -> applyView(integer, toTrack.getCurrentPosition())); ll.addView(fromTrack, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(80))); toTrack = new PositionScrollView(ctx); toTrack.setProgressListener(integer -> { - // TODO: apply only visual? - applyView(fromTrack.getCurrentPosition(), integer); + title.setText(fragment.getContext().getString(R.string.MenuSliceInfoLayers, fromTrack.getCurrentPosition(), integer)); + + ViewUtils.removeCallbacks(applyCallback); + ViewUtils.postOnMainThread(applyCallback = ()-> applyView(fromTrack.getCurrentPosition(), integer), 50); }); toTrack.setListener(integer -> applyView(fromTrack.getCurrentPosition(), integer)); ll.addView(toTrack, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(80))); @@ -267,6 +569,7 @@ public class SliceMenu extends ListBedMenu { super.onCreate(); GCodeViewer viewer = getViewer(); + if (viewer == null) return; long max = viewer.getLayersCount(); fromTrack.setMinMax(1, (int) max); toTrack.setMinMax(1, (int) max); @@ -285,9 +588,6 @@ public class SliceMenu extends ListBedMenu { fromTrack.stopScroll(); toTrack.stopScroll(); - if (getViewer() != null) { - applyView(1, (int) getViewer().getLayersCount()); - } } } } diff --git a/app/src/main/java/ru/ytkab0bp/slicebeam/fragment/BedFragment.java b/app/src/main/java/ru/ytkab0bp/slicebeam/fragment/BedFragment.java index 3fcb0cb..55765c8 100644 --- a/app/src/main/java/ru/ytkab0bp/slicebeam/fragment/BedFragment.java +++ b/app/src/main/java/ru/ytkab0bp/slicebeam/fragment/BedFragment.java @@ -472,6 +472,11 @@ public class BedFragment extends Fragment { super.onApplyTheme(); menuView.setBackgroundColor(ThemesRepo.getColor(android.R.attr.windowBackground)); + for (int i = 0; i < MenuCategory.values().length; i++) { + if (i != currentMenuSlot) { + ThemesRepo.invalidateView(menuMap.get(i).getView()); + } + } } private void constructMenuView(Context ctx, boolean portrait) { diff --git a/app/src/main/java/ru/ytkab0bp/slicebeam/slic3r/GCodeViewer.java b/app/src/main/java/ru/ytkab0bp/slicebeam/slic3r/GCodeViewer.java index 62d4a80..5be18e8 100644 --- a/app/src/main/java/ru/ytkab0bp/slicebeam/slic3r/GCodeViewer.java +++ b/app/src/main/java/ru/ytkab0bp/slicebeam/slic3r/GCodeViewer.java @@ -4,12 +4,54 @@ import static ru.ytkab0bp.slicebeam.utils.DebugUtils.assertTrue; import android.graphics.Color; +import androidx.annotation.IntDef; import androidx.core.util.Pair; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + import ru.ytkab0bp.slicebeam.R; import ru.ytkab0bp.slicebeam.theme.ThemesRepo; public class GCodeViewer { + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + EXTRUSION_ROLE_NONE, + EXTRUSION_ROLE_PERIMETER, + EXTRUSION_ROLE_EXTERNAL_PERIMETER, + EXTRUSION_ROLE_OVERHANG_PERIMETER, + EXTRUSION_ROLE_INTERNAL_INFILL, + EXTRUSION_ROLE_SOLID_INFILL, + EXTRUSION_ROLE_TOP_SOLID_INFILL, + EXTRUSION_ROLE_IRONING, + EXTRUSION_ROLE_BRIDGE_INFILL, + EXTRUSION_ROLE_GAP_FILL, + EXTRUSION_ROLE_SKIRT, + EXTRUSION_ROLE_SUPPORT_MATERIAL, + EXTRUSION_ROLE_SUPPORT_MATERIAL_INTERFACE, + EXTRUSION_ROLE_WIPE_TOWER, + EXTRUSION_ROLE_CUSTOM + }) + public @interface ExtrusionRole{} + + public final static int EXTRUSION_ROLE_NONE = 0, + EXTRUSION_ROLE_PERIMETER = 1, + EXTRUSION_ROLE_EXTERNAL_PERIMETER = 2, + EXTRUSION_ROLE_OVERHANG_PERIMETER = 3, + EXTRUSION_ROLE_INTERNAL_INFILL = 4, + EXTRUSION_ROLE_SOLID_INFILL = 5, + EXTRUSION_ROLE_TOP_SOLID_INFILL = 6, + EXTRUSION_ROLE_IRONING = 7, + EXTRUSION_ROLE_BRIDGE_INFILL = 8, + EXTRUSION_ROLE_GAP_FILL = 9, + EXTRUSION_ROLE_SKIRT = 10, + EXTRUSION_ROLE_SUPPORT_MATERIAL = 11, + EXTRUSION_ROLE_SUPPORT_MATERIAL_INTERFACE = 12, + EXTRUSION_ROLE_WIPE_TOWER = 13, + EXTRUSION_ROLE_CUSTOM = 14, + + EXTRUSION_ROLES_COUNT = 15; + private ThreadLocal viewMatrixBuffer = new ThreadLocal() { @Override protected float[] initialValue() { @@ -54,6 +96,22 @@ public class GCodeViewer { return Native.vgcode_get_layers_count(pointer); } + public float getEstimatedTime() { + return Native.vgcode_get_estimated_time(pointer); + } + + public float getEstimatedTime(@ExtrusionRole int extrusionRole) { + return Native.vgcode_get_estimated_time_role(pointer, extrusionRole); + } + + public boolean isExtrusionRoleVisible(@ExtrusionRole int extrusionRole) { + return Native.vgcode_is_extrusion_role_visible(pointer, extrusionRole); + } + + public void toggleExtrusionRoleVisible(@ExtrusionRole int extrusionRole) { + Native.vgcode_toggle_extrusion_role_visibility(pointer, extrusionRole); + } + public void render(double[] viewMatrix, double[] projectionMatrix) { assertTrue(viewMatrix.length == 16); assertTrue(projectionMatrix.length == 16); diff --git a/app/src/main/java/ru/ytkab0bp/slicebeam/slic3r/Native.java b/app/src/main/java/ru/ytkab0bp/slicebeam/slic3r/Native.java index 4db23a3..d5dc132 100644 --- a/app/src/main/java/ru/ytkab0bp/slicebeam/slic3r/Native.java +++ b/app/src/main/java/ru/ytkab0bp/slicebeam/slic3r/Native.java @@ -84,6 +84,10 @@ class Native { static native void vgcode_render(long ptr, float[] viewMatrix, float[] projectionMatrix); static native void vgcode_set_layers_view_range(long ptr, long min, long max); static native long[] vgcode_get_layers_view_range(long ptr); + static native float vgcode_get_estimated_time(long ptr); + static native float vgcode_get_estimated_time_role(long ptr, int role); + static native boolean vgcode_is_extrusion_role_visible(long ptr, int role); + static native void vgcode_toggle_extrusion_role_visibility(long ptr, int role); static native void vgcode_reset(long ptr); static native void vgcode_release(long ptr); diff --git a/app/src/main/java/ru/ytkab0bp/slicebeam/theme/BeamTheme.java b/app/src/main/java/ru/ytkab0bp/slicebeam/theme/BeamTheme.java index c2d0743..fbdbcbc 100644 --- a/app/src/main/java/ru/ytkab0bp/slicebeam/theme/BeamTheme.java +++ b/app/src/main/java/ru/ytkab0bp/slicebeam/theme/BeamTheme.java @@ -27,13 +27,21 @@ public class BeamTheme { colors.put(R.attr.modelHoverColor, 0xffffffff); colors.put(R.attr.textColorNegative, 0xffff464a); - colors.put(R.attr.gcodeViewerSkirt, 0x7FFF7F); - colors.put(R.attr.gcodeViewerExternalPerimeter, 0xFFFF00); - colors.put(R.attr.gcodeViewerSupportMaterial, 0x7FFF7F); - colors.put(R.attr.gcodeViewerSupportMaterialInterface, 0x7FFF7F); - colors.put(R.attr.gcodeViewerInternalInfill, 0xFF7F7F); - colors.put(R.attr.gcodeViewerSolidInfill, 0xFF7F7F); - colors.put(R.attr.gcodeViewerWipeTower, 0xF7FF7F); + colors.put(R.attr.gcodeViewerNone, 0xFFE6B3B3); + colors.put(R.attr.gcodeViewerPerimeter, 0xFFFFE64D); + colors.put(R.attr.gcodeViewerExternalPerimeter, 0xFFFF7D38); + colors.put(R.attr.gcodeViewerOverhangPerimeter, 0xFF1F1FFF); + colors.put(R.attr.gcodeViewerInternalInfill, 0xFFB03029); + colors.put(R.attr.gcodeViewerSolidInfill, 0xFF9654CC); + colors.put(R.attr.gcodeViewerTopSolidInfill, 0xFFF04040); + colors.put(R.attr.gcodeViewerIroning, 0xFFFF8C69); + colors.put(R.attr.gcodeViewerBridgeInfill, 0xFF4D80BA); + colors.put(R.attr.gcodeViewerGapFill, 0xFF00876E); + colors.put(R.attr.gcodeViewerSkirt, 0xFF00876E); + colors.put(R.attr.gcodeViewerSupportMaterial, 0xFF00FF00); + colors.put(R.attr.gcodeViewerSupportMaterialInterface, 0xFF008000); + colors.put(R.attr.gcodeViewerWipeTower, 0xFFB3E3AB); + colors.put(R.attr.gcodeViewerCustom, 0xFF5ED194); colors.put(R.attr.xTrackColor, 0xffbf0000); colors.put(R.attr.yTrackColor, 0xff00bf00); diff --git a/app/src/main/java/ru/ytkab0bp/slicebeam/view/SegmentsView.java b/app/src/main/java/ru/ytkab0bp/slicebeam/view/SegmentsView.java new file mode 100644 index 0000000..49702e1 --- /dev/null +++ b/app/src/main/java/ru/ytkab0bp/slicebeam/view/SegmentsView.java @@ -0,0 +1,134 @@ +package ru.ytkab0bp.slicebeam.view; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.dynamicanimation.animation.FloatValueHolder; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import java.util.Arrays; + +import ru.ytkab0bp.slicebeam.R; +import ru.ytkab0bp.slicebeam.slic3r.GCodeViewer; +import ru.ytkab0bp.slicebeam.theme.ThemesRepo; +import ru.ytkab0bp.slicebeam.utils.ViewUtils; + +public class SegmentsView extends View { + private Path path = new Path(); + private Paint paint = new Paint(); + private float showProgress; + private boolean isVisible; + private float[] currentValues; + private SpringAnimation currentChangeAnimation; + + public SegmentsView(Context context) { + super(context); + } + + public void startAnimation() { + isVisible = true; + new SpringAnimation(new FloatValueHolder(0)) + .setMinimumVisibleChange(1 / 256f) + .setSpring(new SpringForce(1f) + .setStiffness(1000f) + .setDampingRatio(1f)) + .addUpdateListener((animation, value, velocity) -> { + showProgress = value; + invalidate(); + }) + .start(); + } + + public void setNotVisible() { + isVisible = false; + } + + public void setValues(float[] values) { + if (!isVisible || currentValues == null || currentValues.length != values.length) { + currentValues = values; + invalidate(); + } else { + float[] prevValues = Arrays.copyOf(currentValues, currentValues.length); + currentChangeAnimation = new SpringAnimation(new FloatValueHolder(0)) + .setMinimumVisibleChange(1 / 256f) + .setSpring(new SpringForce(1f) + .setStiffness(1000f) + .setDampingRatio(1f)) + .addUpdateListener((animation, value, velocity) -> { + for (int i = 0; i < currentValues.length; i++) { + currentValues[i] = ViewUtils.lerp(prevValues[i], values[i], value); + } + invalidate(); + }) + .addEndListener((animation, canceled, value, velocity) -> { + if (animation == currentChangeAnimation) { + currentChangeAnimation = null; + } + }); + currentChangeAnimation.start(); + } + } + + public static int mapColor(@GCodeViewer.ExtrusionRole int index) { + switch (index) { + default: + case GCodeViewer.EXTRUSION_ROLE_NONE: + return ThemesRepo.getColor(R.attr.gcodeViewerNone); + case GCodeViewer.EXTRUSION_ROLE_PERIMETER: + return ThemesRepo.getColor(R.attr.gcodeViewerPerimeter); + case GCodeViewer.EXTRUSION_ROLE_EXTERNAL_PERIMETER: + return ThemesRepo.getColor(R.attr.gcodeViewerExternalPerimeter); + case GCodeViewer.EXTRUSION_ROLE_OVERHANG_PERIMETER: + return ThemesRepo.getColor(R.attr.gcodeViewerOverhangPerimeter); + case GCodeViewer.EXTRUSION_ROLE_INTERNAL_INFILL: + return ThemesRepo.getColor(R.attr.gcodeViewerInternalInfill); + case GCodeViewer.EXTRUSION_ROLE_SOLID_INFILL: + return ThemesRepo.getColor(R.attr.gcodeViewerSolidInfill); + case GCodeViewer.EXTRUSION_ROLE_TOP_SOLID_INFILL: + return ThemesRepo.getColor(R.attr.gcodeViewerTopSolidInfill); + case GCodeViewer.EXTRUSION_ROLE_IRONING: + return ThemesRepo.getColor(R.attr.gcodeViewerIroning); + case GCodeViewer.EXTRUSION_ROLE_BRIDGE_INFILL: + return ThemesRepo.getColor(R.attr.gcodeViewerBridgeInfill); + case GCodeViewer.EXTRUSION_ROLE_GAP_FILL: + return ThemesRepo.getColor(R.attr.gcodeViewerGapFill); + case GCodeViewer.EXTRUSION_ROLE_SKIRT: + return ThemesRepo.getColor(R.attr.gcodeViewerSkirt); + case GCodeViewer.EXTRUSION_ROLE_SUPPORT_MATERIAL: + return ThemesRepo.getColor(R.attr.gcodeViewerSupportMaterial); + case GCodeViewer.EXTRUSION_ROLE_SUPPORT_MATERIAL_INTERFACE: + return ThemesRepo.getColor(R.attr.gcodeViewerSupportMaterialInterface); + case GCodeViewer.EXTRUSION_ROLE_WIPE_TOWER: + return ThemesRepo.getColor(R.attr.gcodeViewerWipeTower); + case GCodeViewer.EXTRUSION_ROLE_CUSTOM: + return ThemesRepo.getColor(R.attr.gcodeViewerCustom); + } + } + + @Override + protected void onDraw(@NonNull Canvas canvas) { + super.onDraw(canvas); + + canvas.save(); + path.rewind(); + float cX = getWidth() / 2f; + float l = 0, r = ViewUtils.lerp(0, getWidth(), showProgress); + path.addRoundRect(l, 0, r, getHeight(), ViewUtils.dp(12), ViewUtils.dp(12), Path.Direction.CW); + canvas.clipPath(path); + if (currentValues != null) { + float dw = r - l; + for (int i = 1; i < currentValues.length; i++) { + float prev = currentValues[i - 1]; + float to = currentValues[i]; + paint.setColor(mapColor(i - 1)); + canvas.drawRect(l + prev * dw, 0, l + to * dw, getHeight(), paint); + } + } + canvas.restore(); + } +} diff --git a/app/src/main/jni/slicebeam/beam_native.cpp b/app/src/main/jni/slicebeam/beam_native.cpp index e55cec7..bded8b2 100644 --- a/app/src/main/jni/slicebeam/beam_native.cpp +++ b/app/src/main/jni/slicebeam/beam_native.cpp @@ -1139,4 +1139,77 @@ extern "C" { ref->viewer.shutdown(); delete ref; } + + JNIEXPORT jfloat JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_vgcode_1get_1estimated_1time(JNIEnv* env, jclass, jlong ptr) { + GCodeViewerRef* ref = (GCodeViewerRef*) (intptr_t) ptr; + return ref->viewer.get_estimated_time(); + } + + libvgcode::EGCodeExtrusionRole mapRole(int index) { + libvgcode::EGCodeExtrusionRole crole; + switch (index) { + default: + case 0: + crole = libvgcode::EGCodeExtrusionRole::None; + break; + case 1: + crole = libvgcode::EGCodeExtrusionRole::Perimeter; + break; + case 2: + crole = libvgcode::EGCodeExtrusionRole::ExternalPerimeter; + break; + case 3: + crole = libvgcode::EGCodeExtrusionRole::OverhangPerimeter; + break; + case 4: + crole = libvgcode::EGCodeExtrusionRole::InternalInfill; + break; + case 5: + crole = libvgcode::EGCodeExtrusionRole::SolidInfill; + break; + case 6: + crole = libvgcode::EGCodeExtrusionRole::TopSolidInfill; + break; + case 7: + crole = libvgcode::EGCodeExtrusionRole::Ironing; + break; + case 8: + crole = libvgcode::EGCodeExtrusionRole::BridgeInfill; + break; + case 9: + crole = libvgcode::EGCodeExtrusionRole::GapFill; + break; + case 10: + crole = libvgcode::EGCodeExtrusionRole::Skirt; + break; + case 11: + crole = libvgcode::EGCodeExtrusionRole::SupportMaterial; + break; + case 12: + crole = libvgcode::EGCodeExtrusionRole::SupportMaterialInterface; + break; + case 13: + crole = libvgcode::EGCodeExtrusionRole::WipeTower; + break; + case 14: + crole = libvgcode::EGCodeExtrusionRole::Custom; + break; + } + return crole; + } + + JNIEXPORT jfloat JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_vgcode_1get_1estimated_1time_1role(JNIEnv* env, jclass, jlong ptr, jint role) { + GCodeViewerRef* ref = (GCodeViewerRef*) (intptr_t) ptr; + return ref->viewer.get_extrusion_role_estimated_time(mapRole(role)); + } + + JNIEXPORT jboolean JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_vgcode_1is_1extrusion_1role_1visible(JNIEnv* env, jclass, jlong ptr, jint role) { + GCodeViewerRef* ref = (GCodeViewerRef*) (intptr_t) ptr; + return ref->viewer.is_extrusion_role_visible(mapRole(role)); + } + + JNIEXPORT void JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_vgcode_1toggle_1extrusion_1role_1visibility(JNIEnv* env, jclass, jlong ptr, jint role) { + GCodeViewerRef* ref = (GCodeViewerRef*) (intptr_t) ptr; + ref->viewer.toggle_extrusion_role_visibility(mapRole(role)); + } } \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index aaeeb7e..f5049db 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -68,8 +68,11 @@ Отразить по Z Модификаторы Нарезка - - Слои + Информ.\nо печати + с. + мин. + ч. + Слои Слои %d - %d Экспорт. в файл Отправка diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index b9ca4dc..9aef6b9 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -9,13 +9,21 @@ - + + - - + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b28b4a8..5e28a7e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -69,8 +69,11 @@ Mirror Z Modifiers Slice - - Layers + Print\ninfo + s. + min. + h. + Layers Layers %d - %d Export to file Share