mirror of
https://github.com/Nexoniarz/Fry3DX.git
synced 2026-11-03 12:34:27 +00:00
Smoother: dragging smoothing, rounding or the shading angle in Properties no longer stalls (the angle only re-lights it; rounded and smoothed shapes made on another thread, the newest wanted next, nothing waited on); the picking tree in the object's own space (moving it doesn't make it again; made with the shape on that thread); the grid drawn in pieces, each a pixel wide where it is and fading where its lines crowd together (no crawling towards the horizon), a hair towards the eye so it doesn't fight the ground in Rendered; the near plane further out (less flicker far off); fry_bench to time frames; app.h one declaration a line
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
3c819146af
commit
9a7ed67d80
3 changed files with 332 additions and 116 deletions
330
src/app.h
330
src/app.h
|
|
@ -35,6 +35,7 @@
|
|||
#include <stb_image_write.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
|
|
@ -68,7 +69,8 @@ enum class PrintTool { None, Measure, LayFlat };
|
|||
enum class PaintTool { Brush, Eraser, Fill, Picker, Picture };
|
||||
enum class Elem { Vertex, Edge, Face };
|
||||
enum class View { Perspective, Front, Back, Left, Right, Top, Bottom };
|
||||
enum class Orient { Local, World, Normal }; // which way the gizmo's arrows point
|
||||
enum class Orient { Local, World, Normal };
|
||||
// which way the gizmo's arrows point
|
||||
enum class Pivot { Middle, Origin, Opposite, Bottom }; // what stays put when turning / sizing
|
||||
enum class Shading { Solid, Rendered, Wire };
|
||||
enum class SelShape { Box, Brush, Lasso };
|
||||
|
|
@ -284,16 +286,21 @@ class App : public keel::Game {public:
|
|||
bool dockingUi() const override { return true; }
|
||||
|
||||
// "Spiral_stairs" is "Spiral stairs" (typed in the console).
|
||||
static bool sameCommand(const std::string& name, std::string typed); void init() override;
|
||||
static bool sameCommand(const std::string& name, std::string typed);
|
||||
void init() override;
|
||||
|
||||
// Going into the background: the work is saved now (a phone may close the app while it's
|
||||
// away, to free memory), and a phone's next start carries on with it, view and all.
|
||||
std::string resumeFile() const { return keel::prefPath() + "resume.txt"; } void onBackground() override; void resumeWork();
|
||||
std::string resumeFile() const { return keel::prefPath() + "resume.txt"; }
|
||||
void onBackground() override;
|
||||
void resumeWork();
|
||||
|
||||
void frame(double) override { autosave(); } bool allowQuit() override;
|
||||
void frame(double) override { autosave(); }
|
||||
bool allowQuit() override;
|
||||
void shutdown() override {
|
||||
if (autosaveJob_.valid()) autosaveJob_.wait();
|
||||
} void onEscape() override;
|
||||
}
|
||||
void onEscape() override;
|
||||
|
||||
void render(keel::Renderer& r) override;
|
||||
|
||||
|
|
@ -329,7 +336,8 @@ private:
|
|||
Tool tool_ = Tool::Move;
|
||||
Elem elem_ = Elem::Face;
|
||||
std::set<uint32_t> selected_;
|
||||
uint32_t active_ = 0; // the object last picked (edited in shape editing)
|
||||
uint32_t active_ = 0;
|
||||
// the object last picked (edited in shape editing)
|
||||
uint32_t activeRef_ = 0; // a reference picture picked in the Layers panel
|
||||
std::set<int> selVerts_, selFaces_;
|
||||
std::set<std::pair<int, int>> selEdges_;
|
||||
|
|
@ -411,7 +419,8 @@ private:
|
|||
// side, a hexagon's flats; tall: a rectangle's height)
|
||||
float holeSize_ = 0.2f, holeTall_ = 0.1f, holeDepth_ = 0.1f, holeTurn_ = 0.0f, holeRoom_ = 0.0003f;
|
||||
int holeShape_ = 0, holeSides_ = 32; // a HoleShape
|
||||
int holeMode_ = 0; // 0 all the way through, 1 a pocket, 2 the outline only (a new face)
|
||||
int holeMode_ = 0;
|
||||
// 0 all the way through, 1 a pocket, 2 the outline only (a new face)
|
||||
struct HoleDrag {
|
||||
bool active = false;
|
||||
uint32_t object = 0;
|
||||
|
|
@ -545,7 +554,8 @@ private:
|
|||
uint64_t version = 0; // (the shape key it was made for)
|
||||
bool mirrorOnly = false;
|
||||
Mesh mesh;
|
||||
std::future<Mesh> coming; // (a big one being made on another thread)
|
||||
std::shared_ptr<const fry::TriangleTree> tree; // (made with it, when it was made on another thread)
|
||||
std::future<std::pair<Mesh, std::shared_ptr<const fry::TriangleTree>>> coming; // (a big one being made on another thread)
|
||||
uint64_t comingVersion = 0;
|
||||
bool comingMirror = false;
|
||||
};
|
||||
|
|
@ -579,7 +589,8 @@ private:
|
|||
fry::CommandSearch search_;
|
||||
std::vector<std::string> recentCommands_;
|
||||
std::map<std::string, fry::Chord> myKeys_; // (a command's id: the key you gave it)
|
||||
int pie_ = 0; // 1: the tools round the mouse (Space held), 2: the views (Shift+Space)
|
||||
int pie_ = 0;
|
||||
// 1: the tools round the mouse (Space held), 2: the views (Shift+Space)
|
||||
ImVec2 pieAt_{0, 0};
|
||||
bool pieStays_ = false; // (opened from the menu or console: until a click)
|
||||
std::string settingKey_;
|
||||
|
|
@ -587,7 +598,8 @@ private:
|
|||
// ── Workspaces: the panels as they were, kept under a name ──
|
||||
bool showWorkspaces_ = false, layoutAgain_ = false;
|
||||
std::string pendingLayout_;
|
||||
char workspaceName_[64] = "My workspace"; // (the command waiting for its new key, in Keys and mouse)
|
||||
char workspaceName_[64] = "My workspace";
|
||||
// (the command waiting for its new key, in Keys and mouse)
|
||||
// A plug-in's command waiting for its settings (the form), and a material being named.
|
||||
struct PendingCommand {
|
||||
size_t plugin = 0, command = 0;
|
||||
|
|
@ -611,7 +623,8 @@ private:
|
|||
|
||||
// ───────────────────────────── document ─────────────────────────────
|
||||
|
||||
Object* find(uint32_t id); Reference* findRef(uint32_t id);
|
||||
Object* find(uint32_t id);
|
||||
Reference* findRef(uint32_t id);
|
||||
Object* editing() { return mode_ == Mode::Edit ? find(active_) : nullptr; }
|
||||
void touched(uint32_t id) { versions_[id] = ++versionClock_, dirty_ = true, ++edits_; }
|
||||
// What decides the shape an object shows, as a number: its points and faces, its finishing
|
||||
|
|
@ -620,20 +633,27 @@ private:
|
|||
struct ShapeKey {
|
||||
uint64_t version = 0, key = 0;
|
||||
};
|
||||
std::map<uint32_t, ShapeKey> shapeKeys_; uint64_t shapeKey(const Object& o);
|
||||
std::map<uint32_t, ShapeKey> shapeKeys_;
|
||||
uint64_t shapeKey(const Object& o);
|
||||
void touchAll() {
|
||||
for (const Object& o : scene_.objects) versions_[o.id] = ++versionClock_;
|
||||
}
|
||||
// Its shape has been changed by hand: the library settings no longer apply.
|
||||
void handMade(Object& o) { o.recipe.clear(), o.settings.clear(); } void say(std::string text);
|
||||
void handMade(Object& o) { o.recipe.clear(), o.settings.clear(); }
|
||||
void say(std::string text);
|
||||
|
||||
void pushUndo();
|
||||
// The undo point just made, dropped (what it was for didn't happen): redo as it was before.
|
||||
void dropUndo();
|
||||
// Back to the undo point just made, and it dropped.
|
||||
void takeBack(); void restore(Snapshot& s); void undo(); void redo();
|
||||
void takeBack();
|
||||
void restore(Snapshot& s);
|
||||
void undo();
|
||||
void redo();
|
||||
|
||||
void newScene(); Object& addObject(const std::string& base, Mesh mesh); std::string uniqueName(const std::string& base);
|
||||
void newScene();
|
||||
Object& addObject(const std::string& base, Mesh mesh);
|
||||
std::string uniqueName(const std::string& base);
|
||||
|
||||
// A shape from the library, standing on the ground under the middle of the view.
|
||||
void addShape(const std::string& id, bool undoable = true);
|
||||
|
|
@ -657,21 +677,27 @@ private:
|
|||
// A picked file copied to a file of our own to read ("" if it couldn't be).
|
||||
static std::string fromUri(const std::string& uri, const std::string& name);
|
||||
// A file of ours written into the picked one.
|
||||
static bool toUri(const std::string& local, const std::string& uri); static std::string tempFile(const std::string& name);
|
||||
static bool toUri(const std::string& local, const std::string& uri);
|
||||
static std::string tempFile(const std::string& name);
|
||||
|
||||
void openFile(const std::string& picked); bool saveFile(std::string path);
|
||||
void openFile(const std::string& picked);
|
||||
bool saveFile(std::string path);
|
||||
static std::string sizeText(uintmax_t n) {
|
||||
return n < 10240 ? std::format("{} bytes", n) : n < 10 << 20 ? std::format("{} KB", n >> 10) : std::format("{:.1f} MB", double(n) / 1048576.0);
|
||||
} static std::vector<uint8_t> pngBytes(const Image& im); void exportFile(std::string path, int filter = -1, const std::string& asExt = "");
|
||||
}
|
||||
static std::vector<uint8_t> pngBytes(const Image& im);
|
||||
void exportFile(std::string path, int filter = -1, const std::string& asExt = "");
|
||||
|
||||
// ── Closing with changes, autosave ──
|
||||
|
||||
// New, Open, Quit: asks about saving first if there are changes.
|
||||
void whenSaved(int action); void doAfterAsking(int action);
|
||||
void whenSaved(int action);
|
||||
void doAfterAsking(int action);
|
||||
// A file dropped on the window (or opened with Fry3DX from the desktop): a .fry opens (after
|
||||
// asking about changes), a script goes in the Script panel, a picture goes on as a reference
|
||||
// (or, in the Paint tab, as a sticker on the model).
|
||||
void onFileDropped(const std::string& path) override; void saveQuestion();
|
||||
void onFileDropped(const std::string& path) override;
|
||||
void saveQuestion();
|
||||
std::string autosaveDir() const { return keel::prefPath() + "autosave/"; }
|
||||
// Every fry_autosave minutes (when there are changes and nothing's being dragged): a copy
|
||||
// packed as small as it goes, in the background.
|
||||
|
|
@ -680,44 +706,72 @@ private:
|
|||
void dropAutosave();
|
||||
// Autosaves left behind (it didn't close properly): offered back. Only the newest 15 are
|
||||
// kept; older ones are deleted.
|
||||
void findAutosaves(); void recoverWindow();
|
||||
void findAutosaves();
|
||||
void recoverWindow();
|
||||
|
||||
// A picture of the 3D view only (no panels, gizmos or outlines): the screen is saved a few
|
||||
// frames on, with those hidden, then cut down to the view.
|
||||
void savePicture(std::string path);
|
||||
bool capturing() const { return captureFrames_ > 0; } void captureStep(); static bool loadImageFile(const std::string& path, Image& out);
|
||||
static bool writePng(const std::string& path, const Image& im) { return stbi_write_png(path.c_str(), im.w, im.h, 4, im.px.data(), im.w * 4) != 0; } void addPicture(const std::string& path);
|
||||
bool capturing() const { return captureFrames_ > 0; }
|
||||
void captureStep();
|
||||
static bool loadImageFile(const std::string& path, Image& out);
|
||||
static bool writePng(const std::string& path, const Image& im) { return stbi_write_png(path.c_str(), im.w, im.h, 4, im.px.data(), im.w * 4) != 0; }
|
||||
void addPicture(const std::string& path);
|
||||
|
||||
// SDL's own file dialogs (the desktop's): the answer comes back later, maybe on another thread.
|
||||
void dialog(int kind); void takeDialogResult();
|
||||
void dialog(int kind);
|
||||
void takeDialogResult();
|
||||
|
||||
// ───────────────────────────── modes and tools ─────────────────────────────
|
||||
|
||||
void setTool(Tool t); void enterEdit(); void leaveEdit();
|
||||
void clearElements() { selVerts_.clear(), selFaces_.clear(), selEdges_.clear(); } void selectAllElements(); void selectAll(); void selectNone();
|
||||
void setTool(Tool t);
|
||||
void enterEdit();
|
||||
void leaveEdit();
|
||||
void clearElements() { selVerts_.clear(), selFaces_.clear(), selEdges_.clear(); }
|
||||
void selectAllElements();
|
||||
void selectAll();
|
||||
void selectNone();
|
||||
// The points the selection covers, whichever kind is picked.
|
||||
std::set<int> selectedPoints();
|
||||
// Selects (in the current kind) what these points cover.
|
||||
void selectPoints(const std::set<int>& pts);
|
||||
void growSelection() {
|
||||
if (Object* o = editing()) selectPoints(growVerts(o->mesh, selectedPoints()));
|
||||
} void shrinkSelection();
|
||||
}
|
||||
void shrinkSelection();
|
||||
// Like the picked: faces that face the same way, edges as long, points with as many edges.
|
||||
void selectSimilar();
|
||||
// The picked edges' whole loops (or the rings across them).
|
||||
void loopsOfSelected(bool ring);
|
||||
void selectLinked() {
|
||||
if (Object* o = editing()) selectPoints(linkedVerts(o->mesh, selectedPoints()));
|
||||
} void invertSelection();
|
||||
}
|
||||
void invertSelection();
|
||||
// The edges a rounding would work on: those selected, or round the selected faces, or between
|
||||
// selected points.
|
||||
std::set<std::pair<int, int>> edgesToRound();
|
||||
|
||||
void deleteElements();
|
||||
// After a change that renumbers things: nothing selected, the shape is hand-made now.
|
||||
void changedShape(Object& o, bool keepSelection = false); void extrudeNow(float amount); void insetNow(float amount); void bevelNow(); void subdivideNow(bool smooth); void cutAt(Object& o, std::pair<int, int> edge); void makeFaceNow(); void mergeNow(); void smoothPointsNow(); void startPush(); void pushWindow(); void bridgeNow(); void flipNow();
|
||||
void changedShape(Object& o, bool keepSelection = false);
|
||||
void extrudeNow(float amount);
|
||||
void insetNow(float amount);
|
||||
void bevelNow();
|
||||
void subdivideNow(bool smooth);
|
||||
void cutAt(Object& o, std::pair<int, int> edge);
|
||||
void makeFaceNow();
|
||||
void mergeNow();
|
||||
void smoothPointsNow();
|
||||
void startPush();
|
||||
void pushWindow();
|
||||
void bridgeNow();
|
||||
void flipNow();
|
||||
// Rotation and scale baked into the points (so the object's turn is none and its size is 1).
|
||||
void applyTransform(Object& o); void applyFinish(Object& o); void originToMiddle(Object& o); void originToBottom(Object& o); void mirror(Object& o, int axis);
|
||||
void applyTransform(Object& o);
|
||||
void applyFinish(Object& o);
|
||||
void originToMiddle(Object& o);
|
||||
void originToBottom(Object& o);
|
||||
void mirror(Object& o, int axis);
|
||||
|
||||
|
||||
// ───────────────────────────── tabs ─────────────────────────────
|
||||
|
|
@ -728,16 +782,19 @@ private:
|
|||
// ───────────────────────────── the knife and holes ─────────────────────────────
|
||||
|
||||
// Snapped to steps of 45 degrees from the last point while Ctrl is held.
|
||||
ImVec2 knifeEnd(const ImGuiIO& io) const; static Mesh subMesh(const Mesh& m, const std::set<int>& faces);
|
||||
ImVec2 knifeEnd(const ImGuiIO& io) const;
|
||||
static Mesh subMesh(const Mesh& m, const std::set<int>& faces);
|
||||
// A new object like `from` (its look, place and finish), with another shape.
|
||||
uint32_t objectLike(uint32_t from, Mesh mesh, const std::string& name); void knifeApply(uint32_t id, std::vector<ImVec2> pts);
|
||||
uint32_t objectLike(uint32_t from, Mesh mesh, const std::string& name);
|
||||
void knifeApply(uint32_t id, std::vector<ImVec2> pts);
|
||||
static float objectScale(const Object& o) { return std::max(std::cbrt(std::abs(glm::determinant(glm::mat3(objectMatrix(o))))), 1e-6f); }
|
||||
// How wide (across) a shape is when it reaches `reach` from its middle (dragging it out).
|
||||
float holeAcrossFor(float reach) const {
|
||||
return HoleShape(holeShape_) == HoleShape::Triangle || HoleShape(holeShape_) == HoleShape::Hexagon ? reach * std::sqrt(3.0f) : reach * 2.0f;
|
||||
}
|
||||
// The outline, `across` wide in the object's own size (a rectangle keeps its proportions).
|
||||
std::vector<glm::vec3> holeShapeOutline(const Object& o, int face, const glm::vec3& centre, float across) const; void applyHole(uint32_t id, int face, const glm::vec3& centre, float across);
|
||||
std::vector<glm::vec3> holeShapeOutline(const Object& o, int face, const glm::vec3& centre, float across) const;
|
||||
void applyHole(uint32_t id, int face, const glm::vec3& centre, float across);
|
||||
// The hole tool's outline under the mouse (or being dragged out), in the world.
|
||||
std::vector<glm::vec3> holePreview(const ImGuiIO& io);
|
||||
// Where the mouse is on a face's plane (the object's own space).
|
||||
|
|
@ -749,7 +806,9 @@ private:
|
|||
// out on a picture (made the plain colour of it at first).
|
||||
Object* readyToPaint(uint32_t id);
|
||||
// The object's picture, copied so this change can be undone; the copy is now the object's.
|
||||
Image& changePicture(Object& o); void beginStroke(Object& o, bool in2D); void endStroke();
|
||||
Image& changePicture(Object& o);
|
||||
void beginStroke(Object& o, bool in2D);
|
||||
void endStroke();
|
||||
// Where on its picture a ray hits an object (0..1), or nothing.
|
||||
std::optional<glm::vec2> uvUnder(const Object& o, const Ray& ray);
|
||||
// One dab of the brush on the model: on every face near it, into the picture where that face
|
||||
|
|
@ -759,31 +818,40 @@ private:
|
|||
// it on the screen (the mirrored dab: where its mirror image does, `back` taking it there).
|
||||
void dab3D(Object& o, const glm::vec3& at, float radius, const glm::vec3* eyeAt = nullptr, const glm::mat4* back = nullptr);
|
||||
// The brush at a point of the view: starts a stroke on what's there, or carries one on.
|
||||
void paintAt(ImVec2 mouse, bool start); void pickColor(const Object& o, glm::vec2 uv); void fillAt(Object& o, glm::vec2 uv);
|
||||
void paintAt(ImVec2 mouse, bool start);
|
||||
void pickColor(const Object& o, glm::vec2 uv);
|
||||
void fillAt(Object& o, glm::vec2 uv);
|
||||
|
||||
// ── Sculpting ──
|
||||
|
||||
// The object ready to sculpt: its finishing touches made part of it, and made finer (the same
|
||||
// shape) if it has too few points to push about.
|
||||
Object* readyToSculpt(uint32_t id); void endSculpt();
|
||||
Object* readyToSculpt(uint32_t id);
|
||||
void endSculpt();
|
||||
// The brush at a point of the view: starts a stroke on what's there, or carries one on.
|
||||
void sculptAt(ImVec2 mouse, bool start, bool ctrl, bool shift); void sculptViewport(const ImGuiIO& io);
|
||||
void sculptAt(ImVec2 mouse, bool start, bool ctrl, bool shift);
|
||||
void sculptViewport(const ImGuiIO& io);
|
||||
|
||||
// ── Picture layers ──
|
||||
|
||||
PictureLayer* editLayer();
|
||||
// A picture put on the selected object as a new layer, unlocked to be moved: a sticker on the
|
||||
// surface in the middle of the view, or pinned onto the flat picture.
|
||||
void startFloating(Image img); void floatFromFile(const std::string& path); void pasteFromClipboard();
|
||||
void startFloating(Image img);
|
||||
void floatFromFile(const std::string& path);
|
||||
void pasteFromClipboard();
|
||||
// Enter: it stays where it is, a layer (click it in the Picture panel to move it again).
|
||||
void lockLayer(); void unlockLayer(uint32_t object, int index);
|
||||
void lockLayer();
|
||||
void unlockLayer(uint32_t object, int index);
|
||||
// Esc: a picture just pasted goes again; one unlocked is locked again.
|
||||
void cancelFloating(); void deleteLayer(Object& o, int i);
|
||||
void cancelFloating();
|
||||
void deleteLayer(Object& o, int i);
|
||||
// The layer painted into the painting for good (it can't be moved after).
|
||||
void mergeLayer(Object& o, int i);
|
||||
// The handles of a sticker being moved, in the world: its corners (top-left, top-right,
|
||||
// bottom-right, bottom-left), its turning handle, its middle — on its face's plane.
|
||||
bool stickerHandles(const Object& o, const PictureLayer& l, std::array<glm::vec3, 6>& out); int stickerHandle(const Object& o, const PictureLayer& l, ImVec2 mouse);
|
||||
bool stickerHandles(const Object& o, const PictureLayer& l, std::array<glm::vec3, 6>& out);
|
||||
int stickerHandle(const Object& o, const PictureLayer& l, ImVec2 mouse);
|
||||
// Moving a sticker on the model: it slides over the surface (onto other faces too), its
|
||||
// corners size it, its handle turns it.
|
||||
void stickerMouse(const ImGuiIO& io);
|
||||
|
|
@ -800,12 +868,14 @@ private:
|
|||
// What's picked, as (face, corner) pairs of its layout.
|
||||
std::vector<std::pair<int, int>> layoutPicked(const Object& o, const std::vector<std::vector<int>>& islands) const;
|
||||
// A corner and every other on the same point of the model at the same place on the picture.
|
||||
std::set<std::pair<int, int>> layoutCorner(const Object& o, int f, int c) const; void layoutSelectAll();
|
||||
std::set<std::pair<int, int>> layoutCorner(const Object& o, int f, int c) const;
|
||||
void layoutSelectAll();
|
||||
// The picked corners moved by `to` (picture pixels), painting carried along if wanted.
|
||||
void layoutChange(Object& o, const std::vector<std::vector<glm::vec2>>& from, const std::shared_ptr<const Image>& picture,
|
||||
const std::vector<std::pair<int, int>>& picked, const std::function<glm::vec2(glm::vec2)>& to, bool final);
|
||||
// Buttons' changes (turn, flip, straighten): about the middle of what's picked.
|
||||
void layoutApply(const std::function<glm::vec2(glm::vec2, glm::vec2)>& to); void layoutStraighten();
|
||||
void layoutApply(const std::function<glm::vec2(glm::vec2, glm::vec2)>& to);
|
||||
void layoutStraighten();
|
||||
// Laid out again by itself, what's painted carried over onto the new layout.
|
||||
void layoutAgain(Object& o);
|
||||
|
||||
|
|
@ -954,36 +1024,58 @@ private:
|
|||
}
|
||||
}
|
||||
// In the 3D view: clicking the model picks the frame its face is in (Shift: add).
|
||||
void layoutViewport(const ImGuiIO& io); void layoutOverlay(ImDrawList* dl); void layoutToolbox(float w); void layoutOptions(float w); void layoutProperties(float w);
|
||||
void layoutViewport(const ImGuiIO& io);
|
||||
void layoutOverlay(ImDrawList* dl);
|
||||
void layoutToolbox(float w);
|
||||
void layoutOptions(float w);
|
||||
void layoutProperties(float w);
|
||||
|
||||
// ───────────────────────────── the Print tab ─────────────────────────────
|
||||
|
||||
glm::vec3 bedSize() const; const PrintInfo& printInfo(const Object& o); std::vector<Object*> printTargets(); void drawPrintBed(keel::Renderer& r, const keel::ModelLighting& light); void putOnBed(); void middleOfBed();
|
||||
glm::vec3 bedSize() const;
|
||||
const PrintInfo& printInfo(const Object& o);
|
||||
std::vector<Object*> printTargets();
|
||||
void drawPrintBed(keel::Renderer& r, const keel::ModelLighting& light);
|
||||
void putOnBed();
|
||||
void middleOfBed();
|
||||
// Everything picked made `mm` big along `axis`, keeping its shape, standing where it stood.
|
||||
void printResize(int axis, float mm);
|
||||
// Everything picked made `k` times as big, standing where it stood.
|
||||
void printScale(float k);
|
||||
// The scaler: by a share, to fit the plate, or from the wrong units (a model made in inches,
|
||||
// centimetres or metres comes in 25.4, 10 or 1000 times off).
|
||||
float scalePercent_ = 100.0f; void printScaler(float w); void printSizeWindow(); void printToolbox(float w); void printOptions(float w); void printViewport(const ImGuiIO& io);
|
||||
float scalePercent_ = 100.0f;
|
||||
void printScaler(float w);
|
||||
void printSizeWindow();
|
||||
void printToolbox(float w);
|
||||
void printOptions(float w);
|
||||
void printViewport(const ImGuiIO& io);
|
||||
// Where the mouse is on the model (a corner if near one), or on the bed.
|
||||
std::optional<glm::vec3> measurePoint(ImVec2 mouse);
|
||||
// The slice preview: where the printer goes round at the chosen layer, over the model.
|
||||
void layerOverlay(ImDrawList* dl); void printOverlay(ImDrawList* dl, const ImGuiIO& io); void printProperties(float w);
|
||||
void layerOverlay(ImDrawList* dl);
|
||||
void printOverlay(ImDrawList* dl, const ImGuiIO& io);
|
||||
void printProperties(float w);
|
||||
|
||||
// ───────────────────────────── layers: folders and going to things ─────────────────────────────
|
||||
|
||||
Folder* findFolder(uint32_t id); bool inFolder(uint32_t folder, uint32_t within); std::set<uint32_t> objectsIn(uint32_t folder);
|
||||
Folder* findFolder(uint32_t id);
|
||||
bool inFolder(uint32_t folder, uint32_t within);
|
||||
std::set<uint32_t> objectsIn(uint32_t folder);
|
||||
// The camera brought round to these (as framing them, but keeping the way it looks).
|
||||
void goTo(const std::set<uint32_t>& ids);
|
||||
// The picked objects into a new folder (Ctrl+G): a group, picked and moved together.
|
||||
void groupSelected();
|
||||
// Ungrouped: what's inside goes up a level, the folder goes.
|
||||
void ungroup(uint32_t id); void deleteFolder(uint32_t id);
|
||||
void ungroup(uint32_t id);
|
||||
void deleteFolder(uint32_t id);
|
||||
// A folder's eye or lock: all in it the same.
|
||||
void showFolder(Folder& f, bool on); void lockFolder(Folder& f, bool on); void moveToFolder(const std::set<uint32_t>& ids, uint32_t folder);
|
||||
void showFolder(Folder& f, bool on);
|
||||
void lockFolder(Folder& f, bool on);
|
||||
void moveToFolder(const std::set<uint32_t>& ids, uint32_t folder);
|
||||
// Only these shown (again: everything shown).
|
||||
void showOnly(const std::set<uint32_t>& ids); void folderMenu(uint32_t into, const std::set<uint32_t>& ids, int depth = 0);
|
||||
void showOnly(const std::set<uint32_t>& ids);
|
||||
void folderMenu(uint32_t into, const std::set<uint32_t>& ids, int depth = 0);
|
||||
// What a right-click on an object (or the picked ones) offers.
|
||||
void objectMenu(Object& o);
|
||||
// A row's show / hide or lock button: the picture of how it is now, dim when it's the usual way.
|
||||
|
|
@ -997,14 +1089,19 @@ private:
|
|||
|
||||
// The last picked object with the others added, taken out of it, or only where they overlap
|
||||
// kept; the others go.
|
||||
void combineSelected(Combine op); void makeCopies(); void copiesWindow(); void thickenActive();
|
||||
void combineSelected(Combine op);
|
||||
void makeCopies();
|
||||
void copiesWindow();
|
||||
void thickenActive();
|
||||
// A model made elsewhere: each of its objects, standing on the ground in the middle of the view.
|
||||
void importFile(const std::string& file);
|
||||
|
||||
// ── Spin: an outline turned round an axis (a vase, a wheel, a ring), or a screw ──
|
||||
bool showSpin_ = false, spinWorld_ = false, spinKeep_ = false;
|
||||
int spinAxis_ = 2, spinSteps_ = 48;
|
||||
float spinDegrees_ = 360.0f, spinRise_ = 0.0f; void spinWindow(); void spinSelected();
|
||||
float spinDegrees_ = 360.0f, spinRise_ = 0.0f;
|
||||
void spinWindow();
|
||||
void spinSelected();
|
||||
|
||||
// ── Text as a shape ──
|
||||
bool showText_ = false;
|
||||
|
|
@ -1014,9 +1111,12 @@ private:
|
|||
bool textStanding_ = false;
|
||||
std::string textFontFile_; // (a font of your own, picked from a file)
|
||||
static constexpr const char* kFonts[] = {"Instrument Sans", "Instrument Sans (semi-bold)", "Instrument Sans (bold)", "JetBrains Mono"};
|
||||
static constexpr const char* kFontFiles[] = {"fonts/InstrumentSans-Regular.ttf", "fonts/InstrumentSans-SemiBold.ttf", "fonts/InstrumentSans-Bold.ttf", "fonts/JetBrainsMono-Regular.ttf"}; void textWindow(); void addText();
|
||||
static constexpr const char* kFontFiles[] = {"fonts/InstrumentSans-Regular.ttf", "fonts/InstrumentSans-SemiBold.ttf", "fonts/InstrumentSans-Bold.ttf", "fonts/JetBrainsMono-Regular.ttf"};
|
||||
void textWindow();
|
||||
void addText();
|
||||
|
||||
void thickenWindow(); void cleanFlatFaces();
|
||||
void thickenWindow();
|
||||
void cleanFlatFaces();
|
||||
|
||||
// ───────────────────────────── touch: phones and tablets ─────────────────────────────
|
||||
// A phone: everything drawn bigger, the side panels only when asked for, a bar of big buttons
|
||||
|
|
@ -1026,7 +1126,8 @@ private:
|
|||
|
||||
bool touch() const { return touchMode_; }
|
||||
// How much bigger than its pixels the UI is drawn.
|
||||
float touchScale() const; void touchFrame(ImGuiIO& io);
|
||||
float touchScale() const;
|
||||
void touchFrame(ImGuiIO& io);
|
||||
// The bar along the bottom of the view: big buttons, in rows if the view's narrow.
|
||||
void touchBar();
|
||||
|
||||
|
|
@ -1034,11 +1135,16 @@ private:
|
|||
|
||||
glm::vec3 viewDir() const { return forwardOf(yaw_, pitch_); }
|
||||
float viewHeight() const { return 2.0f * dist_ * std::tan(glm::radians(fov_) * 0.5f); } // metres across the view's height at the target
|
||||
void setView(View v); void leaveFlatView(); void frameBox(glm::vec3 lo, glm::vec3 hi); void frameAll(); void frameSelected();
|
||||
void setView(View v);
|
||||
void leaveFlatView();
|
||||
void frameBox(glm::vec3 lo, glm::vec3 hi);
|
||||
void frameAll();
|
||||
void frameSelected();
|
||||
|
||||
bool mouseInView(const ImGuiIO& io) const;
|
||||
|
||||
void orbitBy(ImVec2 d); void panBy(ImVec2 d);
|
||||
void orbitBy(ImVec2 d);
|
||||
void panBy(ImVec2 d);
|
||||
|
||||
void updateCamera(ImGuiIO& io);
|
||||
// Where the eye is and how the view maps to the screen, from the camera as it is now: again
|
||||
|
|
@ -1050,7 +1156,9 @@ private:
|
|||
|
||||
// World point → screen (false behind the eye).
|
||||
// The window and its view are up (commands given at the start run before the first frame).
|
||||
bool viewReady() const { return ImGui::GetCurrentContext() && viewMax_.x - viewMin_.x > 1 && viewMax_.y - viewMin_.y > 1; } bool project(const glm::vec3& p, ImVec2& out) const; Ray mouseRay(ImVec2 m) const;
|
||||
bool viewReady() const { return ImGui::GetCurrentContext() && viewMax_.x - viewMin_.x > 1 && viewMax_.y - viewMin_.y > 1; }
|
||||
bool project(const glm::vec3& p, ImVec2& out) const;
|
||||
Ray mouseRay(ImVec2 m) const;
|
||||
// How many metres a pixel is, at a point.
|
||||
float pixelSize(const glm::vec3& at) const;
|
||||
|
||||
|
|
@ -1086,11 +1194,10 @@ private:
|
|||
Hit pickObject(const Ray& ray, uint32_t only = 0, bool base = false);
|
||||
// Each object's triangles in the world, in a tree of boxes: made again only when it changes
|
||||
// (its shape, where it is), not each time something's picked.
|
||||
struct Tree {
|
||||
struct Tree { // (in the object's own space: moving it doesn't make it again)
|
||||
uint64_t version = 0;
|
||||
glm::mat4 matrix{0.0f};
|
||||
size_t faces = 0;
|
||||
fry::TriangleTree tree;
|
||||
std::shared_ptr<const fry::TriangleTree> tree;
|
||||
};
|
||||
std::map<std::pair<uint32_t, bool>, Tree> trees_; // (an object's own mesh, or the one shown)
|
||||
const fry::TriangleTree& treeOf(const Object& o, const Mesh& mesh);
|
||||
|
|
@ -1102,7 +1209,8 @@ private:
|
|||
struct Visibility {
|
||||
uint64_t version = 0;
|
||||
glm::vec3 eye{1e30f};
|
||||
glm::mat4 vp{0.0f};
|
||||
glm::mat4 vp{0.0f}, matrix{0.0f};
|
||||
glm::vec3 eyeIn{0.0f}; // (the eye in the object's own space)
|
||||
std::vector<char> point; // 0 not tried yet, 1 seen, 2 hidden
|
||||
};
|
||||
std::map<std::pair<uint32_t, bool>, Visibility> visibility_; // (an object's own mesh, or the one shown)
|
||||
|
|
@ -1113,9 +1221,11 @@ private:
|
|||
uint64_t version = 0;
|
||||
size_t verts = 0, faces = 0;
|
||||
std::vector<std::array<int, 4>> edges; // a, b, a face, the other (or -1)
|
||||
std::vector<glm::vec4> plane; // each face's normal, and its distance along it
|
||||
std::vector<glm::vec4> plane;
|
||||
// each face's normal, and its distance along it
|
||||
};
|
||||
std::map<std::pair<uint32_t, bool>, Adjacency> adjacency_; const Adjacency& adjacencyOf(const Object& o, const Mesh& mesh);
|
||||
std::map<std::pair<uint32_t, bool>, Adjacency> adjacency_;
|
||||
const Adjacency& adjacencyOf(const Object& o, const Mesh& mesh);
|
||||
bool pointSeen(const Object& o, int v) { return xray_ || seen(o, o.mesh, v); }
|
||||
|
||||
// The edge of the edited object nearest the mouse on screen (that can be seen), or (-1, -1).
|
||||
|
|
@ -1133,9 +1243,11 @@ private:
|
|||
|
||||
bool hasSelection();
|
||||
// Every point of the selection, in the world.
|
||||
std::vector<glm::vec3> selectionPoints(); glm::vec3 pivotOfSelection();
|
||||
std::vector<glm::vec3> selectionPoints();
|
||||
glm::vec3 pivotOfSelection();
|
||||
// The selection's box, in a gizmo's axes.
|
||||
std::pair<glm::vec3, glm::vec3> selectionBox(const Axes& a); glm::vec3 selectionMiddle(const Axes& a);
|
||||
std::pair<glm::vec3, glm::vec3> selectionBox(const Axes& a);
|
||||
glm::vec3 selectionMiddle(const Axes& a);
|
||||
Orient& orient() { return mode_ == Mode::Edit ? orientEdit_ : orientObject_; }
|
||||
// The gizmo's axes: the object's own (turning with it), the world's, or (shape editing) the
|
||||
// way the selected faces face — so "up" on a side face is out of that face.
|
||||
|
|
@ -1157,7 +1269,8 @@ private:
|
|||
void startDrag(const ImGuiIO& io, int handle);
|
||||
// The points within reach of the selection (in the world: the object's size counts), each with
|
||||
// how much it follows: a grid of the selected points, so a big mesh stays quick.
|
||||
void findSoft(); void cancelDrag();
|
||||
void findSoft();
|
||||
void cancelDrag();
|
||||
|
||||
void updateDrag(const ImGuiIO& io);
|
||||
// Mirror editing: each point that moved, its twin across the shape's own X (the point that was
|
||||
|
|
@ -1167,21 +1280,25 @@ private:
|
|||
// what's being moved. Marked with a ring.
|
||||
std::optional<glm::vec3> snapTarget(ImVec2 mouse, float within = 22.0f);
|
||||
// The number typed while dragging, if any (just "-" or "." is none yet).
|
||||
std::optional<float> typed() const; void dragNow(const ImGuiIO& io);
|
||||
std::optional<float> typed() const;
|
||||
void dragNow(const ImGuiIO& io);
|
||||
|
||||
// ───────────────────────────── drawing a shape ─────────────────────────────
|
||||
|
||||
// The plane shapes are drawn on: the one the view looks straight at, or the ground.
|
||||
void workPlane(glm::vec3& point, glm::vec3& normal) const;
|
||||
// The plane drawn on: the face under the first point if it was on an object, else the view's.
|
||||
void drawPlane(glm::vec3& p, glm::vec3& n); std::optional<glm::vec3> drawPoint(const ImGuiIO& io);
|
||||
void drawPlane(glm::vec3& p, glm::vec3& n);
|
||||
std::optional<glm::vec3> drawPoint(const ImGuiIO& io);
|
||||
std::string sketchTyped_; // (drawing a shape: the next line's length, typed)
|
||||
// Drawing a shape: numbers typed go to the next line's length, Enter puts the point there.
|
||||
bool sketchKeys(const ImGuiIO& io); void finishShape();
|
||||
bool sketchKeys(const ImGuiIO& io);
|
||||
void finishShape();
|
||||
|
||||
// ───────────────────────────── the viewport's mouse ─────────────────────────────
|
||||
|
||||
void viewport(ImGuiIO& io); void addDrawPoint(const ImGuiIO& io);
|
||||
void viewport(ImGuiIO& io);
|
||||
void addDrawPoint(const ImGuiIO& io);
|
||||
// Painting: on the model under the mouse.
|
||||
void paintViewport(ImGuiIO& io);
|
||||
|
||||
|
|
@ -1209,7 +1326,8 @@ private:
|
|||
|
||||
const Picture& picture(const Reference& ref);
|
||||
|
||||
void loadLogo(); void logoImage(float size);
|
||||
void loadLogo();
|
||||
void logoImage(float size);
|
||||
// A picture of ours in the UI, read the way ImGui's colours are (not turned linear: too dark).
|
||||
void uiImage(ImDrawList* dl, keel::TextureId t, ImVec2 a, ImVec2 b);
|
||||
|
||||
|
|
@ -1220,7 +1338,8 @@ private:
|
|||
// (the Print tab's: the printer's plate and overhangs)
|
||||
// On top of the scene: outlines and wires, points, the gizmo, the selection shapes.
|
||||
// An empty scene: how to start, in the middle of the view (gone once there's anything).
|
||||
void emptyHints(ImDrawList* dl); void overlays();
|
||||
void emptyHints(ImDrawList* dl);
|
||||
void overlays();
|
||||
|
||||
void gizmo(ImDrawList* dl, const ImGuiIO& io);
|
||||
|
||||
|
|
@ -1240,13 +1359,18 @@ private:
|
|||
// ───────────────────────────── commands ─────────────────────────────
|
||||
|
||||
// Everything that can be done, by name: for the command search, keys of your own, the pies.
|
||||
void buildCommands(); void openSearch(); void runCommand(const std::string& id); void commandItem(const char* id); void commandSearch();
|
||||
void buildCommands();
|
||||
void openSearch();
|
||||
void runCommand(const std::string& id); void commandItem(const char* id);
|
||||
void commandSearch();
|
||||
// The pies: the tools (Space), or the views (Shift+Space), round the mouse while it's held.
|
||||
void pieFrame(ImGuiIO& io);
|
||||
// Keys of your own: kept in keys.txt (a line each: the command's id, a tab, the key).
|
||||
void loadMyKeys(); void saveMyKeys();
|
||||
void loadMyKeys();
|
||||
void saveMyKeys();
|
||||
|
||||
void menus(); void menuBar();
|
||||
void menus();
|
||||
void menuBar();
|
||||
|
||||
void addMenuItems();
|
||||
|
||||
|
|
@ -1280,7 +1404,8 @@ private:
|
|||
|
||||
void paintOptions(float w);
|
||||
|
||||
void softChoice(float w); void toolOptions();
|
||||
void softChoice(float w);
|
||||
void toolOptions();
|
||||
|
||||
void layersPanel();
|
||||
|
||||
|
|
@ -1325,22 +1450,47 @@ private:
|
|||
// is taken back.
|
||||
void runScriptNow();
|
||||
// After a script or plug-in command: kept as one undo step, or all taken back on an error.
|
||||
void finishRun(const fry::ScriptResult& r, const std::string& name, bool frame); void openScript(const std::string& path, const std::string& shownName); void saveScript(std::string file);
|
||||
void finishRun(const fry::ScriptResult& r, const std::string& name, bool frame);
|
||||
void openScript(const std::string& path, const std::string& shownName);
|
||||
void saveScript(std::string file);
|
||||
// The scripts that come with it (content/Fry3DX/scripts), as menu items that add one.
|
||||
void exampleScripts(); void exampleItems(bool selectable);
|
||||
void exampleScripts();
|
||||
void exampleItems(bool selectable);
|
||||
|
||||
// ── fry_bench: frames timed while the camera turns or a setting's dragged (finding what's slow) ──
|
||||
struct Bench {
|
||||
std::string what;
|
||||
int left = 0;
|
||||
std::vector<double> ms;
|
||||
} bench_;
|
||||
void benchStep();
|
||||
void benchEnd(std::chrono::steady_clock::time_point began);
|
||||
|
||||
// ── Own materials (materials.txt in the settings folder) ──
|
||||
static std::string ownMaterialsPath() { return keel::prefPath() + "materials.txt"; } void loadOwnMaterials(); void saveOwnMaterials();
|
||||
static std::string ownMaterialsPath() { return keel::prefPath() + "materials.txt"; }
|
||||
void loadOwnMaterials();
|
||||
void saveOwnMaterials();
|
||||
|
||||
// ── Plug-ins: content/Fry3DX/plugins (they come with it) and the settings folder's plugins ──
|
||||
static std::string pluginsFolder() { return keel::prefPath() + "plugins/"; } std::set<std::string> pluginsOff() const; void setPluginOn(const std::string& file, bool on); void loadPlugins(); void copyPlugin(const PluginEntry& e); void openPluginsFolder();
|
||||
bool pluginUsable(size_t i) const { return i < plugins_.size() && plugins_[i].p && plugins_[i].error.empty(); } void pluginMenuItems(const char* menu); void pluginMenuItem(size_t i, size_t c); void runPluginCommand(size_t i, size_t c, const std::vector<double>& values);
|
||||
static std::string pluginsFolder() { return keel::prefPath() + "plugins/"; }
|
||||
std::set<std::string> pluginsOff() const;
|
||||
void setPluginOn(const std::string& file, bool on);
|
||||
void loadPlugins();
|
||||
void copyPlugin(const PluginEntry& e);
|
||||
void openPluginsFolder();
|
||||
bool pluginUsable(size_t i) const { return i < plugins_.size() && plugins_[i].p && plugins_[i].error.empty(); }
|
||||
void pluginMenuItems(const char* menu);
|
||||
void pluginMenuItem(size_t i, size_t c);
|
||||
void runPluginCommand(size_t i, size_t c, const std::vector<double>& values);
|
||||
// A plug-in command's settings, asked for before it runs.
|
||||
void commandForm(); void pluginsWindow(); static int growString(ImGuiInputTextCallbackData* d);
|
||||
void commandForm();
|
||||
void pluginsWindow();
|
||||
static int growString(ImGuiInputTextCallbackData* d);
|
||||
// A one-line field for a std::string (it grows as it's typed into).
|
||||
static bool inputString(const char* label, std::string& text, const char* hint = nullptr) {
|
||||
return ImGui::InputTextWithHint(label, hint ? hint : "", text.data(), text.capacity() + 1, ImGuiInputTextFlags_CallbackResize, growString, &text);
|
||||
} void scriptPanel();
|
||||
}
|
||||
void scriptPanel();
|
||||
|
||||
void helpWindow();
|
||||
|
||||
|
|
@ -1363,11 +1513,17 @@ private:
|
|||
std::vector<int> pts;
|
||||
std::vector<std::vector<int>> rails; // (each point's neighbours that aren't picked: where it can go)
|
||||
ImVec2 start{0, 0};
|
||||
} slide_; void startSlide(); void cancelSlide(); void slideFrame(const ImGuiIO& io);
|
||||
} slide_;
|
||||
void startSlide();
|
||||
void cancelSlide();
|
||||
void slideFrame(const ImGuiIO& io);
|
||||
|
||||
// Looking straight at a reference picture's plane (the front picture from the front or the back...).
|
||||
bool lookingAt(Axis a) const;
|
||||
std::string workspaceDir() const { return keel::prefPath() + "workspaces/"; } void resetLayout(); void workspacesWindow(); static std::string fileSafe(std::string n);
|
||||
std::string workspaceDir() const { return keel::prefPath() + "workspaces/"; }
|
||||
void resetLayout();
|
||||
void workspacesWindow();
|
||||
static std::string fileSafe(std::string n);
|
||||
|
||||
// The steps that can be undone, oldest first: a click goes back (or on) to just after it.
|
||||
void historyWindow();
|
||||
|
|
|
|||
Loading…
Reference in a new issue