diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..7137eb9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,98 @@ +name: 🐞 Bug Report +description: Report an error or unexpected behavior +title: "[BUG] " +labels: [bug] +body: + - type: markdown + attributes: + value: | + **Thank you for reporting a bug!** + Please follow these steps to capture useful info: + + ### How to gather the necessary information: + 🌐 **Browser & Version:** + - Chrome: Click the three dots → Help → About Google Chrome + - Firefox: Click the three bars → Help → About Firefox + - Edge: Click the three dots → Help and feedback → About Microsoft Edge + + 🔗 **Where to find the latest versions of ComfyUI and LayerForge:** + - [ComfyUI Github](https://github.com/comfyanonymous/ComfyUI/releases) + - [LayerForge Github](https://github.com/Azornes/Comfyui-LayerForge/releases/tag/v1.2.4) or [LayerForge from manager Comfyui](https://registry.comfy.org/publishers/azornes/nodes/layerforge) + + Make sure you have the latest versions before reporting an issue. + - type: input + id: environment + attributes: + label: Environment (OS, ComfyUI version, LayerForge version) + placeholder: e.g. Windows 11, ComfyUI v0.3.43, LayerForge v1.2.4 + validations: + required: true + + - type: input + id: browser + attributes: + label: Browser & Version + placeholder: e.g. Chrome 115.0.0, Firefox 120.1.0 + validations: + required: true + + - type: textarea + id: what_happened + attributes: + label: What Happened? + placeholder: Describe the issue you encountered + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Steps to Reproduce + placeholder: | + 1. … + 2. … + 3. … + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected Behavior + placeholder: Describe what you expected to happen + validations: + required: true + + - type: textarea + id: actual + attributes: + label: Actual Behavior + placeholder: Describe what happened instead + validations: + required: true + + - type: textarea + id: console_logs + attributes: + label: Browser Console Logs + description: | + **How to capture logs:** + - **Open console:** + - Chrome/Edge (Win/Linux): `Ctrl+Shift+J` + Mac: `Cmd+Option+J` + - Firefox (Win/Linux): `Ctrl+Shift+K` + Mac: `Cmd+Option+K` + - Safari (Mac): enable **Develop** menu in Preferences → Advanced, then `Cmd+Option+C` + - **Clear console** before reproducing: + - Chrome/Edge: click “🚫 Clear console” or press `Ctrl+L` (Win/Linux) / `Cmd+K` (Mac) + - Firefox: `Ctrl+Shift+L` (newer) or `Ctrl+L` (older) (Win/Linux), Mac: `Cmd+K` + - Safari: click 🗑 icon or press `Cmd+K` / `Ctrl+L` + - Reproduce the issue and paste new logs here. + validations: + required: true + + - type: markdown + attributes: + value: | + **Optional:** You can also **attach a screenshot or video** to demonstrate the issue visually. + To add media, simply drag & drop or paste image/video files into this issue form. GitHub supports common image formats and MP4/GIF files. diff --git a/.github/ISSUE_TEMPLATE/docs_request.yml b/.github/ISSUE_TEMPLATE/docs_request.yml new file mode 100644 index 0000000..5d5c500 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/docs_request.yml @@ -0,0 +1,24 @@ +name: 📝 Documentation Request +description: Suggest improvements or additions to documentation +title: "[Docs] " +labels: [documentation] +body: + - type: input + id: doc_area + attributes: + label: Area of documentation + placeholder: e.g. Getting started, Node API, Deployment guide + validations: + required: true + - type: textarea + id: current_issue + attributes: + label: What's wrong or missing? + placeholder: Describe the gap or confusing part + validations: + required: true + - type: textarea + id: suggested_content + attributes: + label: How should it be improved? + placeholder: Provide concrete suggestions or examples diff --git a/.github/workflows/ComfyUIdownloads.yml b/.github/workflows/ComfyUIdownloads.yml new file mode 100644 index 0000000..d63a7d9 --- /dev/null +++ b/.github/workflows/ComfyUIdownloads.yml @@ -0,0 +1,143 @@ +name: LayerForge Top Downloads Badge + +on: + schedule: + - cron: "0 0,8,16 * * *" + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: gh login + run: echo "${{ secrets.SECRET_TOKEN }}" | gh auth login --with-token + + - name: Query LayerForge API 20 times and find top download + run: | + max_downloads=0 + top_node_json="{}" + + for i in {1..20}; do + echo "Pobieranie danych z próby $i..." + curl -s https://api.comfy.org/nodes/layerforge > tmp_$i.json + + if [ ! -s tmp_$i.json ] || ! jq empty tmp_$i.json 2>/dev/null; then + echo "Błąd: Nieprawidłowy JSON dla próby $i" + continue + fi + + if jq -e 'type == "array"' tmp_$i.json >/dev/null; then + # Przeszukanie wszystkich węzłów w tablicy + node_count=$(jq 'length' tmp_$i.json) + echo "Znaleziono $node_count węzłów w próbie $i" + + for j in $(seq 0 $((node_count - 1))); do + downloads=$(jq -r ".[$j].downloads // 0" tmp_$i.json) + name=$(jq -r ".[$j].name // \"\"" tmp_$i.json) + + if [ "$downloads" -gt "$max_downloads" ]; then + max_downloads=$downloads + top_node_json=$(jq ".[$j]" tmp_$i.json) + echo "Nowe maksimum znalezione: $downloads (węzeł: $name)" + fi + done + else + downloads=$(jq -r '.downloads // 0' tmp_$i.json) + name=$(jq -r '.name // ""' tmp_$i.json) + + if [ "$downloads" -gt "$max_downloads" ]; then + max_downloads=$downloads + top_node_json=$(cat tmp_$i.json) + echo "Nowe maksimum znalezione: $downloads (węzeł: $name)" + fi + fi + + rm -f tmp_$i.json + done + + if [ "$max_downloads" -gt 0 ]; then + echo "$top_node_json" > top_layerforge.json + echo "Najwyższa liczba pobrań: $max_downloads" + echo "Szczegóły węzła:" + jq . top_layerforge.json + else + echo "Błąd: Nie znaleziono żadnych prawidłowych danych" + # Utworzenie domyślnego JSON-a + echo '{"name": "No data", "downloads": 0}' > top_layerforge.json + fi + + - name: create or update gist with top download + id: set_id + run: | + if gh secret list | grep -q "LAYERFORGE_GIST_ID" + then + echo "GIST_ID found" + echo "GIST=${{ secrets.LAYERFORGE_GIST_ID }}" >> $GITHUB_OUTPUT + + # Sprawdzenie czy gist istnieje + if gh gist view ${{ secrets.LAYERFORGE_GIST_ID }} &>/dev/null; then + echo "Gist istnieje, będzie zaktualizowany" + else + echo "Gist nie istnieje, tworzenie nowego" + gist_id=$(gh gist create top_layerforge.json | awk -F / '{print $NF}') + echo $gist_id | gh secret set LAYERFORGE_GIST_ID + echo "GIST=$gist_id" >> $GITHUB_OUTPUT + fi + else + echo "Tworzenie nowego gist" + gist_id=$(gh gist create top_layerforge.json | awk -F / '{print $NF}') + echo $gist_id | gh secret set LAYERFORGE_GIST_ID + echo "GIST=$gist_id" >> $GITHUB_OUTPUT + fi + + - name: create badge if needed + run: | + COUNT=$(jq '.downloads' top_layerforge.json) + NAME=$(jq -r '.name' top_layerforge.json) + if [ ! -f LAYERFORGE.md ]; then + shields="https://img.shields.io/badge/dynamic/json?color=informational&label=TopLayerForge&query=downloads&url=" + url="https://gist.githubusercontent.com/${{ github.actor }}/${{ steps.set_id.outputs.GIST }}/raw/top_layerforge.json" + repo="https://comfy.org" + echo ''> LAYERFORGE.md + echo ' + **Markdown** + + ```markdown' >> LAYERFORGE.md + echo "[![Top LayerForge Node]($shields$url)]($repo)" >> LAYERFORGE.md + echo ' + ``` + + **HTML** + ```html' >> LAYERFORGE.md + echo "Top LayerForge Node" >> LAYERFORGE.md + echo '```' >> LAYERFORGE.md + + git add LAYERFORGE.md + git config --global user.name "GitHub Action" + git config --global user.email "action@github.com" + git commit -m "Create LayerForge badge" + fi + + - name: Update Gist + run: | + # Upewnienie się, że JSON jest poprawny + if jq empty top_layerforge.json 2>/dev/null; then + content=$(jq -c . top_layerforge.json) + echo "{\"description\": \"Top LayerForge Node\", \"files\": {\"top_layerforge.json\": {\"content\": $(jq -Rs . <<< "$content")}}}" > patch.json + + curl -s -X PATCH \ + --user "${{ github.actor }}:${{ secrets.SECRET_TOKEN }}" \ + -H "Content-Type: application/json" \ + -d @patch.json https://api.github.com/gists/${{ steps.set_id.outputs.GIST }} + else + echo "Błąd: Nieprawidłowy JSON w top_layerforge.json" + exit 1 + fi + + - name: Push + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 77f743e..7688ab7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: Auto Release with Version Patch and Commit Message +name: Auto Release with Version Check on: push: @@ -19,28 +19,24 @@ jobs: base=$(grep '^version *= *"' pyproject.toml | sed -E 's/version *= *"([^"]+)"/\1/') echo "base_version=$base" >> $GITHUB_OUTPUT - - name: Determine unique version tag + - name: Check if tag for this version already exists + run: | + TAG="v${{ steps.version.outputs.base_version }}" + git fetch --tags + if git rev-parse "$TAG" >/dev/null 2>&1; then + echo "Tag $TAG already exists. Skipping release." + exit 0 + fi + + - name: Set version tag id: unique_tag run: | - BASE="v${{ steps.version.outputs.base_version }}" - TAG=$BASE - COUNT=0 - - # Fetch remote tags - git fetch --tags - - while git rev-parse "$TAG" >/dev/null 2>&1; do - COUNT=$((COUNT + 1)) - TAG="$BASE.$COUNT" - done - - echo "final_tag=$TAG" >> $GITHUB_OUTPUT + echo "final_tag=v${{ steps.version.outputs.base_version }}" >> $GITHUB_OUTPUT - name: Get latest commit message id: last_commit run: | msg=$(git log -1 --pretty=%B) - # Zamiana nowych linii na \n aby ładnie wyświetlać w release body msg=${msg//$'\n'/\\n} echo "commit_msg=$msg" >> $GITHUB_OUTPUT @@ -51,8 +47,7 @@ jobs: name: Release ${{ steps.unique_tag.outputs.final_tag }} body: | 📦 Release based on pyproject.toml version `${{ steps.version.outputs.base_version }}` - 🔁 Auto-postfix to avoid duplicate tag: `${{ steps.unique_tag.outputs.final_tag }}` - + 📝 Last commit message: ``` ${{ steps.last_commit.outputs.commit_msg }} diff --git a/LAYERFORGE.md b/LAYERFORGE.md new file mode 100644 index 0000000..2a277f6 --- /dev/null +++ b/LAYERFORGE.md @@ -0,0 +1,13 @@ + + + **Markdown** + + ```markdown +[![Top LayerForge Node](https://img.shields.io/badge/dynamic/json?color=informational&label=TopLayerForge&query=downloads&url=https://gist.githubusercontent.com/Azornes/912463d4edd123956066a7aaaa3ef835/raw/top_layerforge.json)](https://comfy.org) + + ``` + + **HTML** + ```html +Top LayerForge Node +``` diff --git a/README.md b/README.md index 17d48cd..2dd2a87 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,17 @@

LayerForge – Advanced Canvas Editor for ComfyUI 🎨

+

LayerForge is an advanced canvas node for ComfyUI, providing a Photoshop-like layer-based editing experience directly within your workflow. It extends the concept of a simple canvas with multi-layer support, masking, blend modes, precise transformations, and seamless integration with other nodes.

+ ComfyUI - Downloads + Downloads GitHub Clones - @@ -18,9 +19,6 @@ JavaScript

- - - ### Why LayerForge? - **Full Creative Control:** Move beyond simple image inputs. Composite, mask, and blend multiple elements without @@ -149,7 +147,7 @@ optional feature and requires a model. > - **Download from**: > - [Hugging Face](https://huggingface.co/ZhengPeng7/BiRefNet/tree/main) (Recommended) -> - [Google Drive](https://drive.google.com/drive/folders/1BCLInCLH89fmTpYoP8Sgs_Eqww28f_wq?usp=sharing) +- [Google Drive](https://drive.google.com/drive/folders/1BCLInCLH89fmTpYoP8Sgs_Eqww28f_wq?usp=sharing) > - **Installation Path**: Place the model file in `ComfyUI/models/BiRefNet/`. --- diff --git a/example_workflows/LayerForge_test_workflow.json b/example_workflows/LayerForge_test_workflow.json new file mode 100644 index 0000000..2122c58 --- /dev/null +++ b/example_workflows/LayerForge_test_workflow.json @@ -0,0 +1,365 @@ +{ + "id": "c7ba7096-c52c-4978-8843-e87ce219b6a8", + "revision": 0, + "last_node_id": 705, + "last_link_id": 1497, + "nodes": [ + { + "id": 368, + "type": "Mask To Image (mtb)", + "pos": [ + -1913.9735107421875, + -3351.5126953125 + ], + "size": [ + 210, + 130 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 1496 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 612 + ] + } + ], + "properties": { + "cnr_id": "comfy-mtb", + "ver": "7e36007933f42c29cca270ae55e0e6866e323633", + "Node name for S&R": "Mask To Image (mtb)", + "widget_ue_connectable": {} + }, + "widgets_values": [ + "#ff0000", + "#000000", + false + ] + }, + { + "id": 442, + "type": "JoinImageWithAlpha", + "pos": [ + -1907.2977294921875, + -3180.562744140625 + ], + "size": [ + 176.86483764648438, + 46 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 1494 + }, + { + "name": "alpha", + "type": "MASK", + "link": 1497 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 1236, + 1465 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.34", + "Node name for S&R": "JoinImageWithAlpha", + "widget_ue_connectable": {} + }, + "widgets_values": [] + }, + { + "id": 369, + "type": "PreviewImage", + "pos": [ + -1699.1021728515625, + -3355.60498046875 + ], + "size": [ + 660.91162109375, + 400.2092590332031 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 612 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.34", + "Node name for S&R": "PreviewImage", + "widget_ue_connectable": {} + }, + "widgets_values": [] + }, + { + "id": 606, + "type": "PreviewImage", + "pos": [ + -1911.126708984375, + -2916.072998046875 + ], + "size": [ + 551.7399291992188, + 546.8018798828125 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 1495 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.34", + "Node name for S&R": "PreviewImage", + "widget_ue_connectable": {} + }, + "widgets_values": [] + }, + { + "id": 603, + "type": "PreviewImage", + "pos": [ + -1344.1650390625, + -2915.117919921875 + ], + "size": [ + 601.4136962890625, + 527.1531372070312 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 1236 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.34", + "Node name for S&R": "PreviewImage", + "widget_ue_connectable": {} + }, + "widgets_values": [] + }, + { + "id": 680, + "type": "SaveImage", + "pos": [ + -1025.9984130859375, + -3357.975341796875 + ], + "size": [ + 278.8309020996094, + 395.84002685546875 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 1465 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.34", + "Node name for S&R": "SaveImage", + "widget_ue_connectable": {} + }, + "widgets_values": [ + "ComfyUI" + ] + }, + { + "id": 701, + "type": "MarkdownNote", + "pos": [ + -3330.08984375, + -3347.998291015625 + ], + "size": [ + 347.055419921875, + 217.8630828857422 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [], + "title": "Known Issue", + "properties": { + "widget_ue_connectable": {} + }, + "widgets_values": [ + "### `node_id` not auto-filled → black output\n\nIn some cases, **ComfyUI doesn't auto-fill the `node_id`** when adding a node.\nAs a result, the node may produce a **completely black image** or not work at all.\n\n**Workaround:**\n\n* Search node ID in ComfyUI settings.\n* In NodesMap check \"Enable node ID display\"\n* Manually enter the correct `node_id` (match the ID shown in the UI).\n\n⚠️ This is a known issue and not yet fixed.\nPlease follow the steps above if your output is black or broken." + ], + "color": "#432", + "bgcolor": "#653" + }, + { + "id": 697, + "type": "CanvasNode", + "pos": [ + -2968.572998046875, + -3347.89306640625 + ], + "size": [ + 1044.9053955078125, + 980.680908203125 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "image", + "type": "IMAGE", + "links": [ + 1494, + 1495 + ] + }, + { + "name": "mask", + "type": "MASK", + "links": [ + 1496, + 1497 + ] + } + ], + "properties": { + "cnr_id": "Comfyui-Ycanvas", + "ver": "f6a491e83bab9481a2cac3367541a3b7803df9ab", + "Node name for S&R": "CanvasNode", + "widget_ue_connectable": {} + }, + "widgets_values": [ + true, + 17, + "697", + "" + ] + } + ], + "links": [ + [ + 612, + 368, + 0, + 369, + 0, + "IMAGE" + ], + [ + 1236, + 442, + 0, + 603, + 0, + "IMAGE" + ], + [ + 1465, + 442, + 0, + 680, + 0, + "IMAGE" + ], + [ + 1494, + 697, + 0, + 442, + 0, + "IMAGE" + ], + [ + 1495, + 697, + 0, + 606, + 0, + "IMAGE" + ], + [ + 1496, + 697, + 1, + 368, + 0, + "MASK" + ], + [ + 1497, + 697, + 1, + 442, + 1, + "MASK" + ] + ], + "groups": [], + "config": {}, + "extra": { + "ds": { + "scale": 0.7972024500000005, + "offset": [ + 3957.401300495613, + 3455.1487103849176 + ] + }, + "ue_links": [], + "links_added_by_ue": [], + "frontendVersion": "1.23.4", + "VHS_latentpreview": false, + "VHS_latentpreviewrate": 0, + "VHS_MetadataImage": true, + "VHS_KeepIntermediate": true + }, + "version": 0.4 +} \ No newline at end of file diff --git a/example_workflows/LayerForge_test_workflow.png b/example_workflows/LayerForge_test_workflow.png new file mode 100644 index 0000000..d173039 Binary files /dev/null and b/example_workflows/LayerForge_test_workflow.png differ diff --git a/pyproject.toml b/pyproject.toml index 979dd9d..e4db044 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "layerforge" description = "Photoshop-like layered canvas editor to your ComfyUI workflow. This node is perfect for complex compositing, inpainting, and outpainting, featuring multi-layer support, masking, blend modes, and precise transformations. Includes optional AI-powered background removal for streamlined image editing." -version = "1.2.4" +version = "1.3.0" license = {file = "LICENSE"} dependencies = ["torch", "torchvision", "transformers", "aiohttp", "numpy", "tqdm", "Pillow"]