From 101437ead3a4ec2e93092d4de31e36d1251ab964 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 4 Feb 2018 23:18:47 -0500 Subject: [PATCH 01/22] reorganized --- data.txt | 90 ++++++++++++++++++++++++++++++++++++++ ps.go | 20 ++++++--- ps_test.go | 12 +++-- {win => scripts}/close.vbs | 0 {win => scripts}/dojs.vbs | 0 {win => scripts}/open.vbs | 0 {win => scripts}/quit.vbs | 1 - {win => scripts}/start.vbs | 0 {win => scripts}/test.jsx | 0 {win => scripts}/test.txt | 0 {win => scripts}/test.vbs | 0 11 files changed, 113 insertions(+), 10 deletions(-) create mode 100644 data.txt rename {win => scripts}/close.vbs (100%) rename {win => scripts}/dojs.vbs (100%) rename {win => scripts}/open.vbs (100%) rename {win => scripts}/quit.vbs (81%) rename {win => scripts}/start.vbs (100%) rename {win => scripts}/test.jsx (100%) rename {win => scripts}/test.txt (100%) rename {win => scripts}/test.vbs (100%) diff --git a/data.txt b/data.txt new file mode 100644 index 0000000..02e8e6f --- /dev/null +++ b/data.txt @@ -0,0 +1,90 @@ +id,name,cost,type,resolve,speed,damage,toughness,life,shorttext,longtext,flavortext,card_image,common,uncommon,rare,action,event,continuous,item,bast,igrath,lilith,vi,ravat,scuttler,tendril,wisp,scinter,tinsel,show_resolve,show_speed,show_tough,show_life,border_normal +Blaze,"Blaze",1,"Event- Channel","+0",0,0,0,0,"Deal 1 to a follower. Draw 1.","Channeled cards can only be played with their leader's resolve","","F:\GitLab\dreamkeepers-psd\Images\Bast\Blaze.png",true,false,false,false,true,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true +Chaotic Blast,"Chaotic Blast",3,"Event- Channel","+0",0,0,0,0,"Bast deals 2 to all other characters. Nearby characters take +1 damage.","Characters in the same lane are considered nearby. Bast does not take damage. Channeled cards can only be played with their leader's resolve","","F:\GitLab\dreamkeepers-psd\Images\Bast\Chaotic Blast.png",false,true,false,false,true,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false +Combust,"Combust",1,"Action- Channel","+0",0,0,0,0,"Deal 2 to a character.","Channeled cards can only be played with their leader's resolve","","F:\GitLab\dreamkeepers-psd\Images\Bast\Combust.png",true,false,false,true,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true +Hot Coals,"Hot Coals",3,"Event- Continuous","+0",0,0,0,0,"If you would draw a card, you may put a discarded Bast channel card into your hand instead.","","","F:\GitLab\dreamkeepers-psd\Images\Bast\Hot Coals.png",false,true,false,false,true,true,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false +Ignite,"Ignite",1,"Action- Channel","+0",0,0,0,0,"Deal 3 to a hero.","Ignite can be played on Leaders, bonus heroes, or deck heroes. Channeled cards can only be played with their leader's resolve.","","F:\GitLab\dreamkeepers-psd\Images\Bast\Ignite.png",true,false,false,true,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true +Kindle,"Kindle",2,"Event","+0",0,0,0,0,"Reveal the top 4 cards of your deck. Put 2 Bast Channel cards from among them into your hand and discard the rest.","","","F:\GitLab\dreamkeepers-psd\Images\Bast\Kindle.png",true,false,false,false,true,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true +Relentless Fury,"Relentless Fury",3,"Event","+0",0,0,0,0,"You may move a Troika hero. That hero gains +1 speed this turn.","Speed is spent to attack, intercept, or redeploy. Move means to put that character in another zone, this does not count as a redeployment.","","F:\GitLab\dreamkeepers-psd\Images\Bast\Relentless Fury.png",false,false,true,false,true,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true +Rush of Anger,"Rush of Anger",2,"Event","+0",0,0,0,0,"A character gets +3/+0 this turn.","You may target any character- even your opponent's. You may play Rush of Anger on a hero or follower.","","F:\GitLab\dreamkeepers-psd\Images\Bast\Rush of Anger.png",false,false,true,false,true,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true +Savage Punch,"Savage Punch",2,"Event","+0",0,0,0,0,"A Troika hero deals its damage to a nearby character.","Characters in the same lane are considered nearby.","","F:\GitLab\dreamkeepers-psd\Images\Bast\Savage Punch.png",false,false,true,false,true,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,tru +Bushwack Squad,"Bushwack Squad",1,"Follower","+0",1,1,1,0,"Uncontested- +2/+1.","If your opponent controls no nearby characters, this character gets +2/+1.","Our chief weapon is surprise! Surprise and guns, guns and surprise... Our two weapons are guns and surprise... And ruthless sarcasm! Our three weapons are...","F:\GitLab\dreamkeepers-psd\Images\Igrath\Bushwack Squad.png",true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,true,false,true +Drastic Measures,"Drastic Measures",1,"Event","+0",0,0,0,0,"Discard a non-leader card from a lane, its controller draws 1.","Deck heroes count as non-leader cards.","","F:\GitLab\dreamkeepers-psd\Images\Igrath\Drastic Measures.png",true,false,false,false,true,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,true +High Ground,"High Ground",3,"Event- Continuous","+0",0,0,0,0,"Uncontested- characters can't deploy or redeploy to this lane.","If your opponent controls nearby characters, this card does nothing. Characters are nearby if they're in the same lane as this card.","","F:\GitLab\dreamkeepers-psd\Images\Igrath\High Ground.png",false,false,true,false,true,true,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false +Loyal Trooper_1,"Loyal Trooper",2,"Follower","+0",1,1,3,0,"Uncontested- +2/+0.","If your opponent controls no nearby characters, this character gets +2/+0.","","F:\GitLab\dreamkeepers-psd\Images\Igrath\Loyal Trooper\Loyal Trooper_Ashworth.png",true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,true,false,true +Loyal Trooper_2,"Loyal Trooper",2,"Follower","+0",1,1,3,0,"Uncontested- +2/+0.","If your opponent controls no nearby characters, this character gets +2/+0.","","F:\GitLab\dreamkeepers-psd\Images\Igrath\Loyal Trooper\Loyal Trooper_Leopard.png",true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,true,false,true +Loyal Trooper_3,"Loyal Trooper",2,"Follower","+0",1,1,3,0,"Uncontested- +2/+0.","If your opponent controls no nearby characters, this character gets +2/+0.","","F:\GitLab\dreamkeepers-psd\Images\Igrath\Loyal Trooper\Loyal Trooper_std.png",true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,true,false,true +Overlooked Advantage,"Overlooked Advantage",1,"Event","+0",0,0,0,0,"Move a character. Draw 1.","Move means to put that character in another zone, this does not count as a redeployment.","","F:\GitLab\dreamkeepers-psd\Images\Igrath\Overlooked Advantage.png",false,false,true,false,true,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,true +Overwhelming Odds,"Overwhelming Odds",4,"Event","+0",0,0,0,0,"Characters in uncontested lanes gain +1 speed this turn.","Lanes are uncontested if your opponent controls no characters in them.","","F:\GitLab\dreamkeepers-psd\Images\Igrath\Overwhelming Odds.png",false,true,false,false,true,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false +Troika Tactician_1,"Troika Tactician",1,"Follower","+0",1,1,2,0,"Uncontested- nearby followers get +1/+1.","If your opponent controls no nearby characters, your nearby followers get +1/+1.","","F:\GitLab\dreamkeepers-psd\Images\Igrath\Troika Tactician\Troika Tactician_Altharin.png",false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,true,false,true +Troika Tactician_2,"Troika Tactician",1,"Follower","+0",1,1,2,0,"Uncontested- nearby followers get +1/+1.","If your opponent controls no nearby characters, your nearby followers get +1/+1.","","F:\GitLab\dreamkeepers-psd\Images\Igrath\Troika Tactician\Troika Tactician_Konig.png",false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,true,false,true +Springtrigger Vanguard_1,"Springtrigger Vanguard",3,"Follower","+0",1,2,3,0,"Uncontested- +2/+1.","If your opponent controls no nearby characters, this character gets +2/+1.","","F:\GitLab\dreamkeepers-psd\Images\Igrath\Springtrigger Vanguard\Springtrigger Vanguard_Goldobsidian.png",false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,true,false,true +Springtrigger Vanguard_2,"Springtrigger Vanguard",3,"Follower","+0",1,2,3,0,"Uncontested- +2/+1.","If your opponent controls no nearby characters, this character gets +2/+1.","","F:\GitLab\dreamkeepers-psd\Images\Igrath\Springtrigger Vanguard\Springtrigger Vanguard_Other.png",false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,true,false,true +Ruskol District Striker_1,"Ruskol District Striker",3,"Follower","+0",1,3,3,0,"Ambush.","You may play this character at any time, even during your opponent's turn.","Please, don’t call it a trap. We prefer the term ‘understated assault.’","F:\GitLab\dreamkeepers-psd\Images\Igrath\Ruskol District Striker\Ruskol District Striker_Arty.png",false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,true,false,true +Ruskol District Striker_2,"Ruskol District Striker",3,"Follower","+0",1,3,3,0,"Ambush.","You may play this character at any time, even during your opponent's turn.","Please, don’t call it a trap. We prefer the term ‘understated assault.’","F:\GitLab\dreamkeepers-psd\Images\Igrath\Ruskol District Striker\Ruskol District Striker_std.png",false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,true,false,tru +Calah's Trusted,"Calah's Trusted",4,"Follower","+0",1,2,4,0,"Guard, Stealth, Deadly.","This character can intercept even when attacked, can only be attacked by nearby characters, and always deals lethal damage.","","F:\GitLab\dreamkeepers-psd\Images\Lilith\Calah's Trusted.png",false,false,true,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,true,true,false,true +Devotion,"Devotion",1,"Event- Continuous","+0",0,0,0,0,"When a Troika character intercepts, they get +1 speed this turn.","Speed is spent to attack, intercept, or redeploy","","F:\GitLab\dreamkeepers-psd\Images\Lilith\Devotion.png",false,false,true,false,true,true,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false +Distraction,"Distraction",1,"Action","+0",0,0,0,0,"Remove a character from a skirmish. It can't be attacked this turn.","That character takes and deals no damage this skirmish.","","F:\GitLab\dreamkeepers-psd\Images\Lilith\Distraction.png",true,false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false +Never Unprepared,"Never Unprepared",2,"Event","+0",0,0,0,0,"Draw 3.","","","F:\GitLab\dreamkeepers-psd\Images\Lilith\Never Unprepared.png",false,true,false,false,true,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false +Peek,"Peek",1,"Event","+0",0,0,0,0,"Prevent 2 damage that would be dealt this turn.","","","F:\GitLab\dreamkeepers-psd\Images\Lilith\Peek.png",true,false,false,false,true,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,true +Quick Thinking,"Quick Thinking",1,"Event","+0",0,0,0,0,"Draw 1, then Replace 2.","You may discard 1 and draw 1, up to 2 times.","","F:\GitLab\dreamkeepers-psd\Images\Lilith\Quick Thinking.png",false,false,true,false,true,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,true +Sabbaton Elite,"Sabbaton Elite",3,"Follower","+0",1,3,4,0,"Guard.","This character can intercept even when attacked.","","F:\GitLab\dreamkeepers-psd\Images\Lilith\Sabbaton Elite.png",true,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,true,true,false,true +Tower Guard,"Tower Guard",2,"Follower","+0",1,1,5,0,"Short range. Guard.","This character can't flank or reinforce. This character can intercept even when attacked.","","F:\GitLab\dreamkeepers-psd\Images\Lilith\Tower Guard.png",true,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,true,true,false,true +Unstable Lifeflow,"Unstable Lifeflow",3,"Event - Leader","+0",0,0,0,0,"Lilith swaps life with a nearby hero.","Both heroes gain or lose life equal to the difference in their life totals.","","F:\GitLab\dreamkeepers-psd\Images\Lilith\Unstable Lifeflow.png",false,true,false,false,true,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,fals +Decaying Ground,"Decaying Ground",2,"Event - Continuous Leader","+0",0,0,0,0,"Start- Deal 1 to all nearby, non-Ravat characters. Nearby characters gain short range.","","","F:\GitLab\dreamkeepers-psd\Images\Ravat\Decaying Ground.png",true,false,false,false,true,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true +Finish the Job,"Finish the Job",1,"Action - Leader","+0",0,0,0,0,"Discard a damaged, non-leader character from play.","A damaged character is one that has been dealt damage this turn.","","F:\GitLab\dreamkeepers-psd\Images\Ravat\Finish the Job.png",true,false,false,true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true +Gruesome Display,"Gruesome Display",2,"Event - Leader","+0",0,0,0,0,"Discard one of your heroes from a lane, if you do, take an extra turn after this one.","","","F:\GitLab\dreamkeepers-psd\Images\Ravat\Gruesome Display.png",false,true,false,false,true,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false +Hunt,"Hunt",2,"Event - Leader","+0",0,0,0,0,"Choose a character. When Ravat damages that character this turn, he gains +1 speed and +1 damage this turn.","","","F:\GitLab\dreamkeepers-psd\Images\Ravat\Hunt.png",false,false,true,false,true,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true +Lacerate,"Lacerate",2,"Event","+0",0,0,0,0,"Deal 4.","","","F:\GitLab\dreamkeepers-psd\Images\Ravat\Lacerate.png",true,false,false,false,true,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true +Lay Waste,"Lay Waste",5,"Event - Continuous","+0",0,0,0,0,"Start- Discard all nearby non-Ravat cards. Nothing can enter this lane.","Non-Ravat means all cards except the Ravat leader card.","","F:\GitLab\dreamkeepers-psd\Images\Ravat\Lay Waste.png",false,true,false,false,true,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false +Mokoi,"Mokoi",3,"Follower","+0",1,4,5,0,"Short range.","","","F:\GitLab\dreamkeepers-psd\Images\Ravat\Mokoi.png",false,false,true,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,true,true,false,true +No Escape,"No Escape",2,"Action - Leader","+0",0,0,0,0,"A nearby character gets (-1) this turn. If it's your turn, move Ravat.","","","F:\GitLab\dreamkeepers-psd\Images\Ravat\No Escape.png",true,false,false,true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true +Terrify,"Terrify",2,"Action - Leader","+0",0,0,0,0,"Nearby characters get -2 speed this turn.","","","F:\GitLab\dreamkeepers-psd\Images\Ravat\Terrify.png",false,false,true,true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,tru +Big Ninja,"Big Ninja",3,"Follower","+0",1,3,3,0,"Ambush. Stealth.","This character can be played at any time, and can only be engaged by nearby characters. ","","F:\GitLab\dreamkeepers-psd\Images\Scinter\Big Ninja.png",false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,true,true,false,true +Smokescreen,"Smokescreen",3,"Event","+0",0,0,0,0,"You decide how characters intercept this turn.","You may choose to have all or none of your opponent's characters intercept as you choose.","","F:\GitLab\dreamkeepers-psd\Images\Scinter\Smokescreen.png",false,true,false,false,true,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false +Nainso,"Nainso",1,"Follower","+0",1,2,2,0,"Stealth","This character can only be engaged by nearby characters.","","F:\GitLab\dreamkeepers-psd\Images\Scinter\Nainso.png",true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,true,true,false,true +Indigo,"Indigo",1,"Follower","+0",2,2,3,0,"Short range.","This character can't attack or flank. (+1) represents +1 speed.","","F:\GitLab\dreamkeepers-psd\Images\Scinter\Indigo.png",true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,true,true,false,true +Rube Goldberg Trap,"Rube Goldberg Trap",2,"Action - Leader","+0",0,0,0,0,"Reveal the top 4 cards of your deck, you may play any traps from among them without paying their costs. Shuffle your deck. Trap- Reveal the top 8 cards instead.","You do not need to pay any costs for free cards.","","F:\GitLab\dreamkeepers-psd\Images\Scinter\Rube Goldberg Trap.png",false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true +Smoke Trap,"Smoke Trap",1,"Action","+0",0,0,0,0,"A character gets (-2) this turn. Trap- Nearby characters also get (-2) this turn.","If you play this on a character that just entered a lane, the Trap ability triggers. Characters with (0) or less can't attack, intercept, or redeploy.","","F:\GitLab\dreamkeepers-psd\Images\Scinter\Smoke Trap.png",false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false +Telewarp Trap,"Telewarp Trap",1,"Action","+0",0,0,0,0,"A character gets +2/+2 this turn. Trap- That character also gets (+1) this turn.","If you play this on a character that just entered a lane, the Trap ability triggers.","","F:\GitLab\dreamkeepers-psd\Images\Scinter\Telewarp Trap.png",false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false +Tripwire Trap,"Tripwire Trap",1,"Action","+0",0,0,0,0,"Deal 2. Trap- Deal 4 instead.","If you play this on a character that just entered a lane, the Trap ability triggers.","","F:\GitLab\dreamkeepers-psd\Images\Scinter\Tripwire Trap.png",true,false,false,true,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false +Warp Trap,"Warp Trap",2,"Action","+0",0,0,0,0,"Return a follower to it's owner's hand. Trap- Move a Troika character.","If you play this on a character that just entered a lane, the Trap ability triggers.","","F:\GitLab\dreamkeepers-psd\Images\Scinter\Warp Trap.png",false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,fals +Dust Hulk,"Dust Hulk",4,"Follower - Sandman","+0",1,3,2,0,"Deploy- You may Scrap X cards to disacard a follower with cost X or less from a lane. Stitch 4.","You may pay 4 to stitch this card from discard onto a Sandman in a lane.that sandman gains this character's damage and toughness (+3/+2).","","F:\GitLab\dreamkeepers-psd\Images\Scuttler\Dust Hulk.png",false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,true +Dust Lacky,"Dust Lacky",2,"Follower - Sandman","+0",1,2,2,0,"When another sandman is discarded from a lane, Scuttler gains 1 resolve. Stitch 4.","You may pay 4 to stitch this card from discard onto a Sandman in a lane.that sandman gains this character's damage and toughness (+2/+2).","","F:\GitLab\dreamkeepers-psd\Images\Scuttler\Dust Lacky.png",true,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,true +Expendable Flunky,"Expendable Flunky",1,"Follower - Sandman","+0",1,1,1,0,"Deploy- Replace 2. Stitch 2.","You may pay 2 to stitch this card from discard onto a Sandman in a lane.that sandman gains this character's damage and toughness (+1/+1).","","F:\GitLab\dreamkeepers-psd\Images\Scuttler\Expendable Flunky.png",true,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,true +Hefty Poppet,"Hefty Poppet",3,"Follower - Sandman","+0",1,2,2,0,"Stitch 2.","You may pay 2 to stitch this card from discard onto a Sandman in a lane.that sandman gains this character's damage and toughness (+2/+2).","","F:\GitLab\dreamkeepers-psd\Images\Scuttler\Hefty Poppet.png",false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,true +Ragpicker Scout,"Ragpicker Scout",1,"Follower - Sandman","+0",1,0,1,0,"Deploy- Draw 1, then Replace 2. Stitch 1.","You may pay 1 to stitch this card from discard onto a Sandman in a lane.that sandman gains this character's damage and toughness (+0/+1).","","F:\GitLab\dreamkeepers-psd\Images\Scuttler\Ragpicker Scout.png",true,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,true +Ragreaper,"Ragreaper",3,"Follower - Sandman","+0",1,4,2,0,"When this character enters a lane, restore a Sandman to full speed. Stitch 6.","You may pay 6 to stitch this card from discard onto a Sandman in a lane.that sandman gains this character's damage and toughness (+4/+2).","","F:\GitLab\dreamkeepers-psd\Images\Scuttler\Ragreaper.png",false,true,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,false +Scuttler's Favorite,"Scuttler's Favorite",4,"Follower - Sandman","+0",1,0,1,0,"Deploy- Stitch all sandmen onto this character. Stitch 1.","You may pay 1 to stitch this card from discard onto a Sandman in a lane.that sandman gains this character's damage and toughness (+0/+1).","","F:\GitLab\dreamkeepers-psd\Images\Scuttler\Scuttler's Favorite.png",false,true,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,false +Seam Parasite,"Seam Parasite",1,"Follower - Sandman","+0",1,1,1,0,"Discard- Stitch this character. Stitch 3.","You may pay 3 to stitch this card from discard onto a Sandman in a lane.that sandman gains this character's damage and toughness (+3/+2).","","F:\GitLab\dreamkeepers-psd\Images\Scuttler\Seam Parasite.png",false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,true +Stitchcraft Pack,"Stitchcraft Pack",1,"Follower - Sandman","+0",1,2,1,0,"When you stitch this character, Replace 2. Stitch 2.","You may pay 2 to stitch this card from discard onto a Sandman in a lane.that sandman gains this character's damage and toughness (+1/+2).","","F:\GitLab\dreamkeepers-psd\Images\Scuttler\Stitchcraft Pack.png",true,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,tru +Bind,"Bind",1,"Action","+0",0,0,0,0,"A follower gets -2/-2 this turn.","Followers in a lane with zero or less toughness must be discarded.","","F:\GitLab\dreamkeepers-psd\Images\Tendril\Bind.png",true,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false +Consume,"Consume",3,"Event","+0",0,0,0,0,"Discard all non-hero cards from each lane.","If a card with resurrect would be discarded this way, it becomes downed instead.","","F:\GitLab\dreamkeepers-psd\Images\Tendril\Consume.png",false,true,false,false,true,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false +Dark Rebirth,"Dark Rebirth",1,"Event","+0",0,0,0,0,"Remove all resurrect counters from cards in each lane.","","In spite of honor, though you’re chaste, Succumb to me and have a taste The lovely feel of teeth in you, Penetrating through and through, An ecstasy that ends in paste","F:\GitLab\dreamkeepers-psd\Images\Tendril\Dark Rebirth.png",false,true,false,false,true,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false +Fatal Blight,"Fatal Blight",2,"Event","+0",0,0,0,0,"Discard a non-leader card from a lane.","If a card with resurrect would be discarded this way, it becomes downed instead.","Breathing faster, Crying laughter, No longer your body’s master","F:\GitLab\dreamkeepers-psd\Images\Tendril\Fatal Blight.png",false,false,true,false,true,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,true +Lyrical Terror,"Lyrical Terror",1,"Follower","+0",1,1,3,0,"Resurrect 2.","If this character would be discarded from play, instead it becomes downed with 2 resurrect counters on it. Start- remove a counter. When the last counter is removed, this character returns to play with depleted speed.","","F:\GitLab\dreamkeepers-psd\Images\Tendril\Lyrical Terror.png",false,false,true,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,true,true,false,true +Monsterous Outgrowth,"Monsterous Outgrowth",2,"Follower","+0",1,3,2,0,"Resurrect 3.","If this character would be discarded from play, instead it becomes downed with 3 resurrect counters on it. Start- remove a counter. When the last counter is removed, this character returns to play with depleted speed.","","F:\GitLab\dreamkeepers-psd\Images\Tendril\Monsterous Outgrowth.png",false,false,true,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,true,true,false,true +Perpetual Nightmare,"Perpetual Nightmare",3,"Follower","+0",1,3,3,0,"Resurrect 3.","If this character would be discarded from play, instead it becomes downed with 3 resurrect counters on it. Start- remove a counter. When the last counter is removed, this character returns to play with depleted speed.","","F:\GitLab\dreamkeepers-psd\Images\Tendril\Perpetual Nightmare.png",true,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,true,true,false,true +Silver-Tongued Spawn,"Silver-Tongued Spawn",2,"Follower","+0",1,2,2,0,"Resurrect 2.","If this character would be discarded from play, instead it becomes downed with 2 resurrect counters on it. Start- remove a counter. When the last counter is removed, this character returns to play with depleted speed.","","F:\GitLab\dreamkeepers-psd\Images\Tendril\Silver-Tongued Spawn.png",false,false,true,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,true,true,false,true +Snuff Out,"Snuff Out",2,"Action","+0",0,0,0,0,"A follower gets -5/-5 this turn.","Followers in a lane with zero or less toughness must be discarded.","","F:\GitLab\dreamkeepers-psd\Images\Tendril\Snuff Out.png",false,false,true,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false +Toxic Entity,"Toxic Entity",1,"Follower","+0",1,1,2,0,"Deadly.","When this character damages another deck character, that character is discarded.","The worm inside We try to hide, But twist or turn the envy burns.","F:\GitLab\dreamkeepers-psd\Images\Tendril\Toxic Entity.png",false,false,true,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,true,true,false,tru +Benevolent Facade,"Benevolent Facade",3,"Event- Continuous Leader","+0",0,0,0,0,"Players play with the top card of their deck revealed. Discard 1: You may play the top card of a deck this turn. {2}: Discard the top card of a deck.","Leader actions must be played with their leader's resolve.","","F:\GitLab\dreamkeepers-psd\Images\Tinsel\Benevolent Facade.png",false,false,true,false,true,true,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,true +Blatant Extortion,"Blatant Extortion",2,"Event- Leader","+0",0,0,0,0,"Negotiate- An opponent discards 2 from their hand or you draw 2.","Your opponent makes choices for this card. If the player has less than 2 cards in hand, they discard as many as possible.","","F:\GitLab\dreamkeepers-psd\Images\Tinsel\Blatant Extortion.png",true,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,true +Deep Connections,"Deep Connections",2,"Event","+0",0,0,0,0,"Sieze a discarded card, you may play it this turn.","You still have to pay all costs to play the card.","","F:\GitLab\dreamkeepers-psd\Images\Tinsel\Deep Connections.png",false,false,true,false,true,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,true +Hair Lash,"Hair Lash",2,"Action- Leader","+0",0,0,0,0,"Tinsel Deals 2. Draw 1.","Leader actions must be played with their leader's resolve.","","F:\GitLab\dreamkeepers-psd\Images\Tinsel\Hair Lash.png",true,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,true +Political Influence,"Political Influence",4,"Event - Leader","+0",0,0,0,0,"Sieze a follwer in a lane.","You gain control of a follower. It doesn't move.","","F:\GitLab\dreamkeepers-psd\Images\Tinsel\Political Influence.png",false,false,true,false,true,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,true +Prison Sentence,"Prison Sentence",1,"Event- Continuous","+0",0,0,0,0,"Play this on a character, that character gets -3 speed. {3}: Discard Prison Sentence. Any player may use this ability.","Characters with (0) or fewer speed can't attack, intercept, or redeploy.","","F:\GitLab\dreamkeepers-psd\Images\Tinsel\Prison Sentence.png",true,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false +Public Figure,"Public Figure",4,"Event- Continuous Leader","+0",0,0,0,0,"When a follower you don't control is discarded from play, you may search it's owner's deck for a card with the same name, seize it, and deploy it.","Leader events must be played with their leader's resolve.","","F:\GitLab\dreamkeepers-psd\Images\Tinsel\Public Figure.png",false,true,false,false,true,true,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false +Status Quo,"Status Quo",2,"Action","+0",0,0,0,0,"Negotiate- Each of your opponent's heroes lose 2 life or get -2 damage this turn.","Your opponent makes choices for this card.","","F:\GitLab\dreamkeepers-psd\Images\Tinsel\Status Quo.png",false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false +Stiletto,"Stiletto",2,"Deck Hero","+1",2,2,0,6,"When this hero deals damage, draw 1. Unique.","If you have more than one character on the battlefield that shares a name with this card, you must choose one of them and discard the rest. (+1) Represents +1 Speed.","","F:\GitLab\dreamkeepers-psd\Images\Tinsel\Stiletto.png",false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,true,fals +Bobby,"Bobby",3,"Deck Hero","+1",1,3,0,9,"{1}: Nearby non-Troika characters get -1/-0 this turn. Unique.","If you have more than one character in a lane that shares a name with this card, you must choose one of them and discard the rest.","","F:\GitLab\dreamkeepers-psd\Images\Vi\Bobby.png",false,false,true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,true,true,false,true,false +Boyfriend,"Boyfriend",4,"Event - Leader","+0",0,0,0,0,"Vi gains +4/+0 and long range this turn.","Leader actions must be played with their leader's resolve. Long range means this character can flank and reinforce from contested lanes.","","F:\GitLab\dreamkeepers-psd\Images\Vi\Boyfriend.png",false,true,false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false +Confrontation,"Confrontation",1,"Event","+0",0,0,0,0,"2 nearby characters skirmish.","Skirmishing characters deal their damage to each other. Characters in the same lane are considered nearby.","","F:\GitLab\dreamkeepers-psd\Images\Vi\Confrontation.png",true,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,true +Karo,"Karo",3,"Deck Hero","+1",1,3,0,9,"-1 Speed: Karo deals 2 to a nearby character. Unique.","{K} represents 1 Karo resolve. If you have more than one character in a lane that shares a name with this card, you must choose one of them and discard the rest.","","F:\GitLab\dreamkeepers-psd\Images\Vi\Karo.png",false,false,true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,true,true,false,true,false +Midrange Mk. III,"Midrange Mk. III",2,"Action","+0",0,0,0,0,"A character gains +2/+0 this turn. A nearby character gains Guard this turn.","Characters with guard can intercept even when attacked.","When in doubt- empty your magazine.","F:\GitLab\dreamkeepers-psd\Images\Vi\Midrange Mk. III.png",false,false,true,true,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false +Rumour,"Rumour",2,"Deck Hero","+1",1,2,0,8,"{1}: Look at the top 3 cards of your deck. You may rearrange them. Unique.","You can't play this card if there's another one in play","","F:\GitLab\dreamkeepers-psd\Images\Vi\Rumour.png",false,false,true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,true,true,false,true,false +Troika Battlebruiser_1,"Troika Battlebruiser",2,"Follower","+0",1,3,4,0,"Short range.","Characters with short range can't flank or reinforce.","","F:\GitLab\dreamkeepers-psd\Images\Vi\Troika Battlebruiser\Troika Battlebruiser.png",true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,true,true,false,true +Troika Battlebruiser_2,"Troika Battlebruiser",2,"Follower","+0",1,3,4,0,"Short range.","Characters with short range can't flank or reinforce.","","F:\GitLab\dreamkeepers-psd\Images\Vi\Troika Battlebruiser\Troika Battlebruiser_Kiaran.png",true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,true,true,false,true +Troika Battlebruiser_3,"Troika Battlebruiser",2,"Follower","+0",1,3,4,0,"Short range.","Characters with short range can't flank or reinforce.","","F:\GitLab\dreamkeepers-psd\Images\Vi\Troika Battlebruiser\Troika Battlebruiser_Kit.png",true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,true,true,false,true +Watch Out!,"Watch Out!",1,"Action","+0",0,0,0,0,"A Troika hero can't affect or be affected by cards this turn. ","The character can't take damage, deal damage, or otherwise be changed by any other card this turn. If they are in a skirmish, they are immediately withdrawn.","No way I can get you up to speed on fire discipline in time- we're focusing on the fundamentals: Do not get shot. Stay down. Stay back. Maintain cover- always,and better yet, stay quiet. If you guys can internalize this, I can keep you alive today. ~Vi","F:\GitLab\dreamkeepers-psd\Images\Vi\Watch Out!.png",true,false,false,true,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false +Wild Gust,"Wild Gust",1,"Action- Leader","+0",0,0,0,0,"Move a nearby character.","Leader actions must be played with their leader's resolve.","Reason with this.","F:\GitLab\dreamkeepers-psd\Images\Vi\Wild Gust.png",false,false,true,true,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,tru diff --git a/ps.go b/ps.go index 31c5f72..4a59170 100644 --- a/ps.go +++ b/ps.go @@ -1,9 +1,14 @@ +// +build windows + +// Package ps lets you manipulate Adobe Photoshop (CS5) from go. +// This is primarily done by calling VBS/Applescript files. +// +// Currently only works on windows package ps import ( "bytes" "fmt" - "os" "os/exec" "path" "runtime" @@ -15,7 +20,13 @@ const ( Opts = "/nologo" ) -var PKGPATH = path.Join(os.Getenv("GOPATH"), "src", "github.com", "sbrow", "ps") +// var PKGPATH = path.Join(os.Getenv("GOPATH"), "src", "github.com", "sbrow", "ps") +var PKGPATH string + +func init() { + _, file, _, _ := runtime.Caller(0) + PKGPATH = path.Dir(file) +} func Start() error { _, err := run("start") @@ -46,22 +57,19 @@ func Wait(msg string) { func run(name string, args ...string) ([]byte, error) { var ext string - var dir string var out bytes.Buffer var stderr bytes.Buffer switch runtime.GOOS { case "windows": ext = ".vbs" - dir = "win" case "darwin": ext = ".applescript" - dir = "mac" } if !strings.HasSuffix(name, ext) { name += ext } - args = append([]string{Opts, path.Join(PKGPATH, dir, name)}, args...) + args = append([]string{Opts, path.Join(PKGPATH, "scripts", name)}, args...) cmd := exec.Command(Cmd, args...) cmd.Stdout = &out cmd.Stderr = &stderr diff --git a/ps_test.go b/ps_test.go index ff497c6..2033e35 100644 --- a/ps_test.go +++ b/ps_test.go @@ -2,12 +2,16 @@ package ps import ( "fmt" - "io/ioutil" - "path" - "strings" + _ "io/ioutil" + _ "path" + _ "strings" "testing" ) +func TestPkgPath(t *testing.T) { + fmt.Println(PKGPATH) +} + // TODO: Comparison borked func TestRun(t *testing.T) { out := []byte("Testing...\n") @@ -40,6 +44,7 @@ func TestWait(t *testing.T) { } // TODO: Comparison borked +/* func TestJS(t *testing.T) { out := "Testing...\n" _, err := Js(path.Join(Folder, "test.jsx"), Folder) @@ -57,3 +62,4 @@ func TestJS(t *testing.T) { t.Fatal(fail) } } +*/ diff --git a/win/close.vbs b/scripts/close.vbs similarity index 100% rename from win/close.vbs rename to scripts/close.vbs diff --git a/win/dojs.vbs b/scripts/dojs.vbs similarity index 100% rename from win/dojs.vbs rename to scripts/dojs.vbs diff --git a/win/open.vbs b/scripts/open.vbs similarity index 100% rename from win/open.vbs rename to scripts/open.vbs diff --git a/win/quit.vbs b/scripts/quit.vbs similarity index 81% rename from win/quit.vbs rename to scripts/quit.vbs index 56244e6..c1f0231 100644 --- a/win/quit.vbs +++ b/scripts/quit.vbs @@ -1,7 +1,6 @@ ' Close Photoshop Set appRef = CreateObject("Photoshop.Application") -wScript.echo appRef.Documents.Count Do While appRef.Documents.Count > 0 appRef.ActiveDocument.Close(2) Loop diff --git a/win/start.vbs b/scripts/start.vbs similarity index 100% rename from win/start.vbs rename to scripts/start.vbs diff --git a/win/test.jsx b/scripts/test.jsx similarity index 100% rename from win/test.jsx rename to scripts/test.jsx diff --git a/win/test.txt b/scripts/test.txt similarity index 100% rename from win/test.txt rename to scripts/test.txt diff --git a/win/test.vbs b/scripts/test.vbs similarity index 100% rename from win/test.vbs rename to scripts/test.vbs From ef1fb47d0e6d4372577314c1fa89fa701ded37b9 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 4 Feb 2018 23:31:16 -0500 Subject: [PATCH 02/22] fixes --- data.txt | 90 ------------------------------------------------ ps.go | 15 ++++---- ps_test.go | 9 +++-- scripts/quit.vbs | 2 +- scripts/test.txt | 1 - 5 files changed, 16 insertions(+), 101 deletions(-) delete mode 100644 data.txt delete mode 100644 scripts/test.txt diff --git a/data.txt b/data.txt deleted file mode 100644 index 02e8e6f..0000000 --- a/data.txt +++ /dev/null @@ -1,90 +0,0 @@ -id,name,cost,type,resolve,speed,damage,toughness,life,shorttext,longtext,flavortext,card_image,common,uncommon,rare,action,event,continuous,item,bast,igrath,lilith,vi,ravat,scuttler,tendril,wisp,scinter,tinsel,show_resolve,show_speed,show_tough,show_life,border_normal -Blaze,"Blaze",1,"Event- Channel","+0",0,0,0,0,"Deal 1 to a follower. Draw 1.","Channeled cards can only be played with their leader's resolve","","F:\GitLab\dreamkeepers-psd\Images\Bast\Blaze.png",true,false,false,false,true,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true -Chaotic Blast,"Chaotic Blast",3,"Event- Channel","+0",0,0,0,0,"Bast deals 2 to all other characters. Nearby characters take +1 damage.","Characters in the same lane are considered nearby. Bast does not take damage. Channeled cards can only be played with their leader's resolve","","F:\GitLab\dreamkeepers-psd\Images\Bast\Chaotic Blast.png",false,true,false,false,true,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false -Combust,"Combust",1,"Action- Channel","+0",0,0,0,0,"Deal 2 to a character.","Channeled cards can only be played with their leader's resolve","","F:\GitLab\dreamkeepers-psd\Images\Bast\Combust.png",true,false,false,true,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true -Hot Coals,"Hot Coals",3,"Event- Continuous","+0",0,0,0,0,"If you would draw a card, you may put a discarded Bast channel card into your hand instead.","","","F:\GitLab\dreamkeepers-psd\Images\Bast\Hot Coals.png",false,true,false,false,true,true,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false -Ignite,"Ignite",1,"Action- Channel","+0",0,0,0,0,"Deal 3 to a hero.","Ignite can be played on Leaders, bonus heroes, or deck heroes. Channeled cards can only be played with their leader's resolve.","","F:\GitLab\dreamkeepers-psd\Images\Bast\Ignite.png",true,false,false,true,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true -Kindle,"Kindle",2,"Event","+0",0,0,0,0,"Reveal the top 4 cards of your deck. Put 2 Bast Channel cards from among them into your hand and discard the rest.","","","F:\GitLab\dreamkeepers-psd\Images\Bast\Kindle.png",true,false,false,false,true,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true -Relentless Fury,"Relentless Fury",3,"Event","+0",0,0,0,0,"You may move a Troika hero. That hero gains +1 speed this turn.","Speed is spent to attack, intercept, or redeploy. Move means to put that character in another zone, this does not count as a redeployment.","","F:\GitLab\dreamkeepers-psd\Images\Bast\Relentless Fury.png",false,false,true,false,true,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true -Rush of Anger,"Rush of Anger",2,"Event","+0",0,0,0,0,"A character gets +3/+0 this turn.","You may target any character- even your opponent's. You may play Rush of Anger on a hero or follower.","","F:\GitLab\dreamkeepers-psd\Images\Bast\Rush of Anger.png",false,false,true,false,true,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true -Savage Punch,"Savage Punch",2,"Event","+0",0,0,0,0,"A Troika hero deals its damage to a nearby character.","Characters in the same lane are considered nearby.","","F:\GitLab\dreamkeepers-psd\Images\Bast\Savage Punch.png",false,false,true,false,true,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,tru -Bushwack Squad,"Bushwack Squad",1,"Follower","+0",1,1,1,0,"Uncontested- +2/+1.","If your opponent controls no nearby characters, this character gets +2/+1.","Our chief weapon is surprise! Surprise and guns, guns and surprise... Our two weapons are guns and surprise... And ruthless sarcasm! Our three weapons are...","F:\GitLab\dreamkeepers-psd\Images\Igrath\Bushwack Squad.png",true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,true,false,true -Drastic Measures,"Drastic Measures",1,"Event","+0",0,0,0,0,"Discard a non-leader card from a lane, its controller draws 1.","Deck heroes count as non-leader cards.","","F:\GitLab\dreamkeepers-psd\Images\Igrath\Drastic Measures.png",true,false,false,false,true,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,true -High Ground,"High Ground",3,"Event- Continuous","+0",0,0,0,0,"Uncontested- characters can't deploy or redeploy to this lane.","If your opponent controls nearby characters, this card does nothing. Characters are nearby if they're in the same lane as this card.","","F:\GitLab\dreamkeepers-psd\Images\Igrath\High Ground.png",false,false,true,false,true,true,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false -Loyal Trooper_1,"Loyal Trooper",2,"Follower","+0",1,1,3,0,"Uncontested- +2/+0.","If your opponent controls no nearby characters, this character gets +2/+0.","","F:\GitLab\dreamkeepers-psd\Images\Igrath\Loyal Trooper\Loyal Trooper_Ashworth.png",true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,true,false,true -Loyal Trooper_2,"Loyal Trooper",2,"Follower","+0",1,1,3,0,"Uncontested- +2/+0.","If your opponent controls no nearby characters, this character gets +2/+0.","","F:\GitLab\dreamkeepers-psd\Images\Igrath\Loyal Trooper\Loyal Trooper_Leopard.png",true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,true,false,true -Loyal Trooper_3,"Loyal Trooper",2,"Follower","+0",1,1,3,0,"Uncontested- +2/+0.","If your opponent controls no nearby characters, this character gets +2/+0.","","F:\GitLab\dreamkeepers-psd\Images\Igrath\Loyal Trooper\Loyal Trooper_std.png",true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,true,false,true -Overlooked Advantage,"Overlooked Advantage",1,"Event","+0",0,0,0,0,"Move a character. Draw 1.","Move means to put that character in another zone, this does not count as a redeployment.","","F:\GitLab\dreamkeepers-psd\Images\Igrath\Overlooked Advantage.png",false,false,true,false,true,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,true -Overwhelming Odds,"Overwhelming Odds",4,"Event","+0",0,0,0,0,"Characters in uncontested lanes gain +1 speed this turn.","Lanes are uncontested if your opponent controls no characters in them.","","F:\GitLab\dreamkeepers-psd\Images\Igrath\Overwhelming Odds.png",false,true,false,false,true,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false -Troika Tactician_1,"Troika Tactician",1,"Follower","+0",1,1,2,0,"Uncontested- nearby followers get +1/+1.","If your opponent controls no nearby characters, your nearby followers get +1/+1.","","F:\GitLab\dreamkeepers-psd\Images\Igrath\Troika Tactician\Troika Tactician_Altharin.png",false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,true,false,true -Troika Tactician_2,"Troika Tactician",1,"Follower","+0",1,1,2,0,"Uncontested- nearby followers get +1/+1.","If your opponent controls no nearby characters, your nearby followers get +1/+1.","","F:\GitLab\dreamkeepers-psd\Images\Igrath\Troika Tactician\Troika Tactician_Konig.png",false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,true,false,true -Springtrigger Vanguard_1,"Springtrigger Vanguard",3,"Follower","+0",1,2,3,0,"Uncontested- +2/+1.","If your opponent controls no nearby characters, this character gets +2/+1.","","F:\GitLab\dreamkeepers-psd\Images\Igrath\Springtrigger Vanguard\Springtrigger Vanguard_Goldobsidian.png",false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,true,false,true -Springtrigger Vanguard_2,"Springtrigger Vanguard",3,"Follower","+0",1,2,3,0,"Uncontested- +2/+1.","If your opponent controls no nearby characters, this character gets +2/+1.","","F:\GitLab\dreamkeepers-psd\Images\Igrath\Springtrigger Vanguard\Springtrigger Vanguard_Other.png",false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,true,false,true -Ruskol District Striker_1,"Ruskol District Striker",3,"Follower","+0",1,3,3,0,"Ambush.","You may play this character at any time, even during your opponent's turn.","Please, don’t call it a trap. We prefer the term ‘understated assault.’","F:\GitLab\dreamkeepers-psd\Images\Igrath\Ruskol District Striker\Ruskol District Striker_Arty.png",false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,true,false,true -Ruskol District Striker_2,"Ruskol District Striker",3,"Follower","+0",1,3,3,0,"Ambush.","You may play this character at any time, even during your opponent's turn.","Please, don’t call it a trap. We prefer the term ‘understated assault.’","F:\GitLab\dreamkeepers-psd\Images\Igrath\Ruskol District Striker\Ruskol District Striker_std.png",false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,true,false,tru -Calah's Trusted,"Calah's Trusted",4,"Follower","+0",1,2,4,0,"Guard, Stealth, Deadly.","This character can intercept even when attacked, can only be attacked by nearby characters, and always deals lethal damage.","","F:\GitLab\dreamkeepers-psd\Images\Lilith\Calah's Trusted.png",false,false,true,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,true,true,false,true -Devotion,"Devotion",1,"Event- Continuous","+0",0,0,0,0,"When a Troika character intercepts, they get +1 speed this turn.","Speed is spent to attack, intercept, or redeploy","","F:\GitLab\dreamkeepers-psd\Images\Lilith\Devotion.png",false,false,true,false,true,true,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false -Distraction,"Distraction",1,"Action","+0",0,0,0,0,"Remove a character from a skirmish. It can't be attacked this turn.","That character takes and deals no damage this skirmish.","","F:\GitLab\dreamkeepers-psd\Images\Lilith\Distraction.png",true,false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false -Never Unprepared,"Never Unprepared",2,"Event","+0",0,0,0,0,"Draw 3.","","","F:\GitLab\dreamkeepers-psd\Images\Lilith\Never Unprepared.png",false,true,false,false,true,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false -Peek,"Peek",1,"Event","+0",0,0,0,0,"Prevent 2 damage that would be dealt this turn.","","","F:\GitLab\dreamkeepers-psd\Images\Lilith\Peek.png",true,false,false,false,true,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,true -Quick Thinking,"Quick Thinking",1,"Event","+0",0,0,0,0,"Draw 1, then Replace 2.","You may discard 1 and draw 1, up to 2 times.","","F:\GitLab\dreamkeepers-psd\Images\Lilith\Quick Thinking.png",false,false,true,false,true,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,true -Sabbaton Elite,"Sabbaton Elite",3,"Follower","+0",1,3,4,0,"Guard.","This character can intercept even when attacked.","","F:\GitLab\dreamkeepers-psd\Images\Lilith\Sabbaton Elite.png",true,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,true,true,false,true -Tower Guard,"Tower Guard",2,"Follower","+0",1,1,5,0,"Short range. Guard.","This character can't flank or reinforce. This character can intercept even when attacked.","","F:\GitLab\dreamkeepers-psd\Images\Lilith\Tower Guard.png",true,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,true,true,false,true -Unstable Lifeflow,"Unstable Lifeflow",3,"Event - Leader","+0",0,0,0,0,"Lilith swaps life with a nearby hero.","Both heroes gain or lose life equal to the difference in their life totals.","","F:\GitLab\dreamkeepers-psd\Images\Lilith\Unstable Lifeflow.png",false,true,false,false,true,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,fals -Decaying Ground,"Decaying Ground",2,"Event - Continuous Leader","+0",0,0,0,0,"Start- Deal 1 to all nearby, non-Ravat characters. Nearby characters gain short range.","","","F:\GitLab\dreamkeepers-psd\Images\Ravat\Decaying Ground.png",true,false,false,false,true,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true -Finish the Job,"Finish the Job",1,"Action - Leader","+0",0,0,0,0,"Discard a damaged, non-leader character from play.","A damaged character is one that has been dealt damage this turn.","","F:\GitLab\dreamkeepers-psd\Images\Ravat\Finish the Job.png",true,false,false,true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true -Gruesome Display,"Gruesome Display",2,"Event - Leader","+0",0,0,0,0,"Discard one of your heroes from a lane, if you do, take an extra turn after this one.","","","F:\GitLab\dreamkeepers-psd\Images\Ravat\Gruesome Display.png",false,true,false,false,true,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false -Hunt,"Hunt",2,"Event - Leader","+0",0,0,0,0,"Choose a character. When Ravat damages that character this turn, he gains +1 speed and +1 damage this turn.","","","F:\GitLab\dreamkeepers-psd\Images\Ravat\Hunt.png",false,false,true,false,true,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true -Lacerate,"Lacerate",2,"Event","+0",0,0,0,0,"Deal 4.","","","F:\GitLab\dreamkeepers-psd\Images\Ravat\Lacerate.png",true,false,false,false,true,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true -Lay Waste,"Lay Waste",5,"Event - Continuous","+0",0,0,0,0,"Start- Discard all nearby non-Ravat cards. Nothing can enter this lane.","Non-Ravat means all cards except the Ravat leader card.","","F:\GitLab\dreamkeepers-psd\Images\Ravat\Lay Waste.png",false,true,false,false,true,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false -Mokoi,"Mokoi",3,"Follower","+0",1,4,5,0,"Short range.","","","F:\GitLab\dreamkeepers-psd\Images\Ravat\Mokoi.png",false,false,true,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,true,true,false,true -No Escape,"No Escape",2,"Action - Leader","+0",0,0,0,0,"A nearby character gets (-1) this turn. If it's your turn, move Ravat.","","","F:\GitLab\dreamkeepers-psd\Images\Ravat\No Escape.png",true,false,false,true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true -Terrify,"Terrify",2,"Action - Leader","+0",0,0,0,0,"Nearby characters get -2 speed this turn.","","","F:\GitLab\dreamkeepers-psd\Images\Ravat\Terrify.png",false,false,true,true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,tru -Big Ninja,"Big Ninja",3,"Follower","+0",1,3,3,0,"Ambush. Stealth.","This character can be played at any time, and can only be engaged by nearby characters. ","","F:\GitLab\dreamkeepers-psd\Images\Scinter\Big Ninja.png",false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,true,true,false,true -Smokescreen,"Smokescreen",3,"Event","+0",0,0,0,0,"You decide how characters intercept this turn.","You may choose to have all or none of your opponent's characters intercept as you choose.","","F:\GitLab\dreamkeepers-psd\Images\Scinter\Smokescreen.png",false,true,false,false,true,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false -Nainso,"Nainso",1,"Follower","+0",1,2,2,0,"Stealth","This character can only be engaged by nearby characters.","","F:\GitLab\dreamkeepers-psd\Images\Scinter\Nainso.png",true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,true,true,false,true -Indigo,"Indigo",1,"Follower","+0",2,2,3,0,"Short range.","This character can't attack or flank. (+1) represents +1 speed.","","F:\GitLab\dreamkeepers-psd\Images\Scinter\Indigo.png",true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,true,true,false,true -Rube Goldberg Trap,"Rube Goldberg Trap",2,"Action - Leader","+0",0,0,0,0,"Reveal the top 4 cards of your deck, you may play any traps from among them without paying their costs. Shuffle your deck. Trap- Reveal the top 8 cards instead.","You do not need to pay any costs for free cards.","","F:\GitLab\dreamkeepers-psd\Images\Scinter\Rube Goldberg Trap.png",false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true -Smoke Trap,"Smoke Trap",1,"Action","+0",0,0,0,0,"A character gets (-2) this turn. Trap- Nearby characters also get (-2) this turn.","If you play this on a character that just entered a lane, the Trap ability triggers. Characters with (0) or less can't attack, intercept, or redeploy.","","F:\GitLab\dreamkeepers-psd\Images\Scinter\Smoke Trap.png",false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false -Telewarp Trap,"Telewarp Trap",1,"Action","+0",0,0,0,0,"A character gets +2/+2 this turn. Trap- That character also gets (+1) this turn.","If you play this on a character that just entered a lane, the Trap ability triggers.","","F:\GitLab\dreamkeepers-psd\Images\Scinter\Telewarp Trap.png",false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false -Tripwire Trap,"Tripwire Trap",1,"Action","+0",0,0,0,0,"Deal 2. Trap- Deal 4 instead.","If you play this on a character that just entered a lane, the Trap ability triggers.","","F:\GitLab\dreamkeepers-psd\Images\Scinter\Tripwire Trap.png",true,false,false,true,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false -Warp Trap,"Warp Trap",2,"Action","+0",0,0,0,0,"Return a follower to it's owner's hand. Trap- Move a Troika character.","If you play this on a character that just entered a lane, the Trap ability triggers.","","F:\GitLab\dreamkeepers-psd\Images\Scinter\Warp Trap.png",false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,fals -Dust Hulk,"Dust Hulk",4,"Follower - Sandman","+0",1,3,2,0,"Deploy- You may Scrap X cards to disacard a follower with cost X or less from a lane. Stitch 4.","You may pay 4 to stitch this card from discard onto a Sandman in a lane.that sandman gains this character's damage and toughness (+3/+2).","","F:\GitLab\dreamkeepers-psd\Images\Scuttler\Dust Hulk.png",false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,true -Dust Lacky,"Dust Lacky",2,"Follower - Sandman","+0",1,2,2,0,"When another sandman is discarded from a lane, Scuttler gains 1 resolve. Stitch 4.","You may pay 4 to stitch this card from discard onto a Sandman in a lane.that sandman gains this character's damage and toughness (+2/+2).","","F:\GitLab\dreamkeepers-psd\Images\Scuttler\Dust Lacky.png",true,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,true -Expendable Flunky,"Expendable Flunky",1,"Follower - Sandman","+0",1,1,1,0,"Deploy- Replace 2. Stitch 2.","You may pay 2 to stitch this card from discard onto a Sandman in a lane.that sandman gains this character's damage and toughness (+1/+1).","","F:\GitLab\dreamkeepers-psd\Images\Scuttler\Expendable Flunky.png",true,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,true -Hefty Poppet,"Hefty Poppet",3,"Follower - Sandman","+0",1,2,2,0,"Stitch 2.","You may pay 2 to stitch this card from discard onto a Sandman in a lane.that sandman gains this character's damage and toughness (+2/+2).","","F:\GitLab\dreamkeepers-psd\Images\Scuttler\Hefty Poppet.png",false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,true -Ragpicker Scout,"Ragpicker Scout",1,"Follower - Sandman","+0",1,0,1,0,"Deploy- Draw 1, then Replace 2. Stitch 1.","You may pay 1 to stitch this card from discard onto a Sandman in a lane.that sandman gains this character's damage and toughness (+0/+1).","","F:\GitLab\dreamkeepers-psd\Images\Scuttler\Ragpicker Scout.png",true,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,true -Ragreaper,"Ragreaper",3,"Follower - Sandman","+0",1,4,2,0,"When this character enters a lane, restore a Sandman to full speed. Stitch 6.","You may pay 6 to stitch this card from discard onto a Sandman in a lane.that sandman gains this character's damage and toughness (+4/+2).","","F:\GitLab\dreamkeepers-psd\Images\Scuttler\Ragreaper.png",false,true,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,false -Scuttler's Favorite,"Scuttler's Favorite",4,"Follower - Sandman","+0",1,0,1,0,"Deploy- Stitch all sandmen onto this character. Stitch 1.","You may pay 1 to stitch this card from discard onto a Sandman in a lane.that sandman gains this character's damage and toughness (+0/+1).","","F:\GitLab\dreamkeepers-psd\Images\Scuttler\Scuttler's Favorite.png",false,true,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,false -Seam Parasite,"Seam Parasite",1,"Follower - Sandman","+0",1,1,1,0,"Discard- Stitch this character. Stitch 3.","You may pay 3 to stitch this card from discard onto a Sandman in a lane.that sandman gains this character's damage and toughness (+3/+2).","","F:\GitLab\dreamkeepers-psd\Images\Scuttler\Seam Parasite.png",false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,true -Stitchcraft Pack,"Stitchcraft Pack",1,"Follower - Sandman","+0",1,2,1,0,"When you stitch this character, Replace 2. Stitch 2.","You may pay 2 to stitch this card from discard onto a Sandman in a lane.that sandman gains this character's damage and toughness (+1/+2).","","F:\GitLab\dreamkeepers-psd\Images\Scuttler\Stitchcraft Pack.png",true,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,tru -Bind,"Bind",1,"Action","+0",0,0,0,0,"A follower gets -2/-2 this turn.","Followers in a lane with zero or less toughness must be discarded.","","F:\GitLab\dreamkeepers-psd\Images\Tendril\Bind.png",true,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false -Consume,"Consume",3,"Event","+0",0,0,0,0,"Discard all non-hero cards from each lane.","If a card with resurrect would be discarded this way, it becomes downed instead.","","F:\GitLab\dreamkeepers-psd\Images\Tendril\Consume.png",false,true,false,false,true,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false -Dark Rebirth,"Dark Rebirth",1,"Event","+0",0,0,0,0,"Remove all resurrect counters from cards in each lane.","","In spite of honor, though you’re chaste, Succumb to me and have a taste The lovely feel of teeth in you, Penetrating through and through, An ecstasy that ends in paste","F:\GitLab\dreamkeepers-psd\Images\Tendril\Dark Rebirth.png",false,true,false,false,true,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false -Fatal Blight,"Fatal Blight",2,"Event","+0",0,0,0,0,"Discard a non-leader card from a lane.","If a card with resurrect would be discarded this way, it becomes downed instead.","Breathing faster, Crying laughter, No longer your body’s master","F:\GitLab\dreamkeepers-psd\Images\Tendril\Fatal Blight.png",false,false,true,false,true,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,true -Lyrical Terror,"Lyrical Terror",1,"Follower","+0",1,1,3,0,"Resurrect 2.","If this character would be discarded from play, instead it becomes downed with 2 resurrect counters on it. Start- remove a counter. When the last counter is removed, this character returns to play with depleted speed.","","F:\GitLab\dreamkeepers-psd\Images\Tendril\Lyrical Terror.png",false,false,true,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,true,true,false,true -Monsterous Outgrowth,"Monsterous Outgrowth",2,"Follower","+0",1,3,2,0,"Resurrect 3.","If this character would be discarded from play, instead it becomes downed with 3 resurrect counters on it. Start- remove a counter. When the last counter is removed, this character returns to play with depleted speed.","","F:\GitLab\dreamkeepers-psd\Images\Tendril\Monsterous Outgrowth.png",false,false,true,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,true,true,false,true -Perpetual Nightmare,"Perpetual Nightmare",3,"Follower","+0",1,3,3,0,"Resurrect 3.","If this character would be discarded from play, instead it becomes downed with 3 resurrect counters on it. Start- remove a counter. When the last counter is removed, this character returns to play with depleted speed.","","F:\GitLab\dreamkeepers-psd\Images\Tendril\Perpetual Nightmare.png",true,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,true,true,false,true -Silver-Tongued Spawn,"Silver-Tongued Spawn",2,"Follower","+0",1,2,2,0,"Resurrect 2.","If this character would be discarded from play, instead it becomes downed with 2 resurrect counters on it. Start- remove a counter. When the last counter is removed, this character returns to play with depleted speed.","","F:\GitLab\dreamkeepers-psd\Images\Tendril\Silver-Tongued Spawn.png",false,false,true,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,true,true,false,true -Snuff Out,"Snuff Out",2,"Action","+0",0,0,0,0,"A follower gets -5/-5 this turn.","Followers in a lane with zero or less toughness must be discarded.","","F:\GitLab\dreamkeepers-psd\Images\Tendril\Snuff Out.png",false,false,true,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false -Toxic Entity,"Toxic Entity",1,"Follower","+0",1,1,2,0,"Deadly.","When this character damages another deck character, that character is discarded.","The worm inside We try to hide, But twist or turn the envy burns.","F:\GitLab\dreamkeepers-psd\Images\Tendril\Toxic Entity.png",false,false,true,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,true,true,false,tru -Benevolent Facade,"Benevolent Facade",3,"Event- Continuous Leader","+0",0,0,0,0,"Players play with the top card of their deck revealed. Discard 1: You may play the top card of a deck this turn. {2}: Discard the top card of a deck.","Leader actions must be played with their leader's resolve.","","F:\GitLab\dreamkeepers-psd\Images\Tinsel\Benevolent Facade.png",false,false,true,false,true,true,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,true -Blatant Extortion,"Blatant Extortion",2,"Event- Leader","+0",0,0,0,0,"Negotiate- An opponent discards 2 from their hand or you draw 2.","Your opponent makes choices for this card. If the player has less than 2 cards in hand, they discard as many as possible.","","F:\GitLab\dreamkeepers-psd\Images\Tinsel\Blatant Extortion.png",true,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,true -Deep Connections,"Deep Connections",2,"Event","+0",0,0,0,0,"Sieze a discarded card, you may play it this turn.","You still have to pay all costs to play the card.","","F:\GitLab\dreamkeepers-psd\Images\Tinsel\Deep Connections.png",false,false,true,false,true,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,true -Hair Lash,"Hair Lash",2,"Action- Leader","+0",0,0,0,0,"Tinsel Deals 2. Draw 1.","Leader actions must be played with their leader's resolve.","","F:\GitLab\dreamkeepers-psd\Images\Tinsel\Hair Lash.png",true,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,true -Political Influence,"Political Influence",4,"Event - Leader","+0",0,0,0,0,"Sieze a follwer in a lane.","You gain control of a follower. It doesn't move.","","F:\GitLab\dreamkeepers-psd\Images\Tinsel\Political Influence.png",false,false,true,false,true,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,true -Prison Sentence,"Prison Sentence",1,"Event- Continuous","+0",0,0,0,0,"Play this on a character, that character gets -3 speed. {3}: Discard Prison Sentence. Any player may use this ability.","Characters with (0) or fewer speed can't attack, intercept, or redeploy.","","F:\GitLab\dreamkeepers-psd\Images\Tinsel\Prison Sentence.png",true,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false -Public Figure,"Public Figure",4,"Event- Continuous Leader","+0",0,0,0,0,"When a follower you don't control is discarded from play, you may search it's owner's deck for a card with the same name, seize it, and deploy it.","Leader events must be played with their leader's resolve.","","F:\GitLab\dreamkeepers-psd\Images\Tinsel\Public Figure.png",false,true,false,false,true,true,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false -Status Quo,"Status Quo",2,"Action","+0",0,0,0,0,"Negotiate- Each of your opponent's heroes lose 2 life or get -2 damage this turn.","Your opponent makes choices for this card.","","F:\GitLab\dreamkeepers-psd\Images\Tinsel\Status Quo.png",false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false -Stiletto,"Stiletto",2,"Deck Hero","+1",2,2,0,6,"When this hero deals damage, draw 1. Unique.","If you have more than one character on the battlefield that shares a name with this card, you must choose one of them and discard the rest. (+1) Represents +1 Speed.","","F:\GitLab\dreamkeepers-psd\Images\Tinsel\Stiletto.png",false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,true,fals -Bobby,"Bobby",3,"Deck Hero","+1",1,3,0,9,"{1}: Nearby non-Troika characters get -1/-0 this turn. Unique.","If you have more than one character in a lane that shares a name with this card, you must choose one of them and discard the rest.","","F:\GitLab\dreamkeepers-psd\Images\Vi\Bobby.png",false,false,true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,true,true,false,true,false -Boyfriend,"Boyfriend",4,"Event - Leader","+0",0,0,0,0,"Vi gains +4/+0 and long range this turn.","Leader actions must be played with their leader's resolve. Long range means this character can flank and reinforce from contested lanes.","","F:\GitLab\dreamkeepers-psd\Images\Vi\Boyfriend.png",false,true,false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false -Confrontation,"Confrontation",1,"Event","+0",0,0,0,0,"2 nearby characters skirmish.","Skirmishing characters deal their damage to each other. Characters in the same lane are considered nearby.","","F:\GitLab\dreamkeepers-psd\Images\Vi\Confrontation.png",true,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,true -Karo,"Karo",3,"Deck Hero","+1",1,3,0,9,"-1 Speed: Karo deals 2 to a nearby character. Unique.","{K} represents 1 Karo resolve. If you have more than one character in a lane that shares a name with this card, you must choose one of them and discard the rest.","","F:\GitLab\dreamkeepers-psd\Images\Vi\Karo.png",false,false,true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,true,true,false,true,false -Midrange Mk. III,"Midrange Mk. III",2,"Action","+0",0,0,0,0,"A character gains +2/+0 this turn. A nearby character gains Guard this turn.","Characters with guard can intercept even when attacked.","When in doubt- empty your magazine.","F:\GitLab\dreamkeepers-psd\Images\Vi\Midrange Mk. III.png",false,false,true,true,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false -Rumour,"Rumour",2,"Deck Hero","+1",1,2,0,8,"{1}: Look at the top 3 cards of your deck. You may rearrange them. Unique.","You can't play this card if there's another one in play","","F:\GitLab\dreamkeepers-psd\Images\Vi\Rumour.png",false,false,true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,true,true,false,true,false -Troika Battlebruiser_1,"Troika Battlebruiser",2,"Follower","+0",1,3,4,0,"Short range.","Characters with short range can't flank or reinforce.","","F:\GitLab\dreamkeepers-psd\Images\Vi\Troika Battlebruiser\Troika Battlebruiser.png",true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,true,true,false,true -Troika Battlebruiser_2,"Troika Battlebruiser",2,"Follower","+0",1,3,4,0,"Short range.","Characters with short range can't flank or reinforce.","","F:\GitLab\dreamkeepers-psd\Images\Vi\Troika Battlebruiser\Troika Battlebruiser_Kiaran.png",true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,true,true,false,true -Troika Battlebruiser_3,"Troika Battlebruiser",2,"Follower","+0",1,3,4,0,"Short range.","Characters with short range can't flank or reinforce.","","F:\GitLab\dreamkeepers-psd\Images\Vi\Troika Battlebruiser\Troika Battlebruiser_Kit.png",true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,true,true,false,true -Watch Out!,"Watch Out!",1,"Action","+0",0,0,0,0,"A Troika hero can't affect or be affected by cards this turn. ","The character can't take damage, deal damage, or otherwise be changed by any other card this turn. If they are in a skirmish, they are immediately withdrawn.","No way I can get you up to speed on fire discipline in time- we're focusing on the fundamentals: Do not get shot. Stay down. Stay back. Maintain cover- always,and better yet, stay quiet. If you guys can internalize this, I can keep you alive today. ~Vi","F:\GitLab\dreamkeepers-psd\Images\Vi\Watch Out!.png",true,false,false,true,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false -Wild Gust,"Wild Gust",1,"Action- Leader","+0",0,0,0,0,"Move a nearby character.","Leader actions must be played with their leader's resolve.","Reason with this.","F:\GitLab\dreamkeepers-psd\Images\Vi\Wild Gust.png",false,false,true,true,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,tru diff --git a/ps.go b/ps.go index 4a59170..f87d1f4 100644 --- a/ps.go +++ b/ps.go @@ -20,30 +20,33 @@ const ( Opts = "/nologo" ) -// var PKGPATH = path.Join(os.Getenv("GOPATH"), "src", "github.com", "sbrow", "ps") -var PKGPATH string +var pkgpath string func init() { _, file, _, _ := runtime.Caller(0) - PKGPATH = path.Dir(file) + pkgpath = path.Dir(file) } +// Open photoshop. func Start() error { _, err := run("start") return err } +// Open a file. func Open(path string) ([]byte, error) { return run("open", path) } +// Close the active document. func Close() error { _, err := run("close") return err } -func Quit() ([]byte, error) { - return run("quit") +// Quit photoshop with save status. +func Quit(save int) ([]byte, error) { + return run("quit", string(save)) } func Js(args ...string) ([]byte, error) { @@ -69,7 +72,7 @@ func run(name string, args ...string) ([]byte, error) { if !strings.HasSuffix(name, ext) { name += ext } - args = append([]string{Opts, path.Join(PKGPATH, "scripts", name)}, args...) + args = append([]string{Opts, path.Join(pkgpath, "scripts", name)}, args...) cmd := exec.Command(Cmd, args...) cmd.Stdout = &out cmd.Stderr = &stderr diff --git a/ps_test.go b/ps_test.go index 2033e35..a9c5f6b 100644 --- a/ps_test.go +++ b/ps_test.go @@ -3,13 +3,14 @@ package ps import ( "fmt" _ "io/ioutil" - _ "path" + "os" + "path" _ "strings" "testing" ) func TestPkgPath(t *testing.T) { - fmt.Println(PKGPATH) + fmt.Println(pkgpath) } // TODO: Comparison borked @@ -22,6 +23,8 @@ func TestRun(t *testing.T) { if string(msg) == string(out) { fail := fmt.Sprintf("run(test)\ngot:\t\"%s\"\nwant:\t\"%s\"\n", msg, out) t.Fatal(fail) + } else { + os.Remove(path.Join(pkgpath, "scripts", "test.txt")) } } @@ -36,7 +39,7 @@ func TestQuit(t *testing.T) { if testing.Short() { t.Skip("Skipping \"TestQuit\"") } - Quit() + Quit(2) } func TestWait(t *testing.T) { diff --git a/scripts/quit.vbs b/scripts/quit.vbs index c1f0231..b4dbca4 100644 --- a/scripts/quit.vbs +++ b/scripts/quit.vbs @@ -2,7 +2,7 @@ Set appRef = CreateObject("Photoshop.Application") Do While appRef.Documents.Count > 0 - appRef.ActiveDocument.Close(2) + appRef.ActiveDocument.Close(wScript.Arguments(0)) Loop appRef.Quit() \ No newline at end of file diff --git a/scripts/test.txt b/scripts/test.txt deleted file mode 100644 index 5862d9e..0000000 --- a/scripts/test.txt +++ /dev/null @@ -1 +0,0 @@ -Testing... From e63a2f940bb042321dbf4aa12ad2a378d729260f Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 4 Feb 2018 23:48:28 -0500 Subject: [PATCH 03/22] test updates --- ps_test.go | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/ps_test.go b/ps_test.go index a9c5f6b..e3e8b06 100644 --- a/ps_test.go +++ b/ps_test.go @@ -13,8 +13,15 @@ func TestPkgPath(t *testing.T) { fmt.Println(pkgpath) } +func TestOpen(t *testing.T) { + if testing.Short() { + t.Skip("Skipping \"TestOpen\"") + } + Open("F:\\GitLab\\dreamkeepers-psd\\Template009.1.psd") +} + // TODO: Comparison borked -func TestRun(t *testing.T) { +/*func TestRun(t *testing.T) { out := []byte("Testing...\n") msg, err := run("test") if err != nil { @@ -27,14 +34,7 @@ func TestRun(t *testing.T) { os.Remove(path.Join(pkgpath, "scripts", "test.txt")) } } - -func TestOpen(t *testing.T) { - if testing.Short() { - t.Skip("Skipping \"TestOpen\"") - } - Open("F:\\GitLab\\dreamkeepers-psd\\Template009.1.psd") -} - +*/ func TestQuit(t *testing.T) { if testing.Short() { t.Skip("Skipping \"TestQuit\"") @@ -44,11 +44,11 @@ func TestQuit(t *testing.T) { func TestWait(t *testing.T) { Wait("Waiting...") + fmt.Println() } // TODO: Comparison borked -/* -func TestJS(t *testing.T) { +/*func TestJS(t *testing.T) { out := "Testing...\n" _, err := Js(path.Join(Folder, "test.jsx"), Folder) if err != nil { @@ -64,5 +64,4 @@ func TestJS(t *testing.T) { fail := fmt.Sprintf("TestJS failed.\ngot:\t\"%s\"\nwant:\t\"%s\"", f, out) t.Fatal(fail) } -} -*/ +}*/ From eb3aa7e393103edae7638c74ff520ec9bdee126d Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 5 Feb 2018 00:02:04 -0500 Subject: [PATCH 04/22] updates --- ps.go | 14 +++++++++----- ps_test.go | 7 +++++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/ps.go b/ps.go index f87d1f4..46050fb 100644 --- a/ps.go +++ b/ps.go @@ -15,16 +15,20 @@ import ( "strings" ) -const ( - Cmd = "cscript.exe" - Opts = "/nologo" -) - +var Cmd string +var Opts string var pkgpath string func init() { _, file, _, _ := runtime.Caller(0) pkgpath = path.Dir(file) + switch runtime.GOOS { + case "windows": + Cmd = "cscript.exe" + Opts = "/nologo" + case "darwin": + Cmd = "osacript" + } } // Open photoshop. diff --git a/ps_test.go b/ps_test.go index e3e8b06..d412dd5 100644 --- a/ps_test.go +++ b/ps_test.go @@ -4,13 +4,16 @@ import ( "fmt" _ "io/ioutil" "os" - "path" + "path/filepath" _ "strings" "testing" ) func TestPkgPath(t *testing.T) { - fmt.Println(pkgpath) + out := filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "sbrow", "ps") + if filepath.Join(pkgpath) != out { + t.Fatal(filepath.Join(pkgpath), out) + } } func TestOpen(t *testing.T) { From 39e09f9bce293ebd87055d5e77554ac0dae507d6 Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 5 Feb 2018 00:25:51 -0500 Subject: [PATCH 05/22] edits --- ps.go | 1 + 1 file changed, 1 insertion(+) diff --git a/ps.go b/ps.go index 46050fb..0136426 100644 --- a/ps.go +++ b/ps.go @@ -56,6 +56,7 @@ func Quit(save int) ([]byte, error) { func Js(args ...string) ([]byte, error) { return run("dojs", args...) } + func Wait(msg string) { fmt.Print(msg) var input string From fe871d48d639b544ae6b318b94601649342eb5e9 Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 5 Feb 2018 15:50:56 -0500 Subject: [PATCH 06/22] improved robustness more tests added. --- ps.go | 54 +++++++++++++++++++++++++++++++++++++++--------- ps_test.go | 41 +++++++++++++++--------------------- scripts/dojs.vbs | 12 +++++------ scripts/test.jsx | 9 ++++---- scripts/test.vbs | 4 +++- 5 files changed, 74 insertions(+), 46 deletions(-) diff --git a/ps.go b/ps.go index 0136426..ef271f1 100644 --- a/ps.go +++ b/ps.go @@ -1,5 +1,3 @@ -// +build windows - // Package ps lets you manipulate Adobe Photoshop (CS5) from go. // This is primarily done by calling VBS/Applescript files. // @@ -8,9 +6,12 @@ package ps import ( "bytes" + "errors" "fmt" + "io/ioutil" + "os" "os/exec" - "path" + "path/filepath" "runtime" "strings" ) @@ -21,7 +22,7 @@ var pkgpath string func init() { _, file, _, _ := runtime.Caller(0) - pkgpath = path.Dir(file) + pkgpath = filepath.Dir(file) switch runtime.GOOS { case "windows": Cmd = "cscript.exe" @@ -53,8 +54,29 @@ func Quit(save int) ([]byte, error) { return run("quit", string(save)) } -func Js(args ...string) ([]byte, error) { - return run("dojs", args...) +func Js(path string, args ...string) ([]byte, error) { + // Temp file for js to output to. + outpath := filepath.Join(os.Getenv("TEMP"), "js_out.txt") + args = append([]string{outpath}, args...) + + // If passed a script by name, assume it's in the default folder. + if filepath.Dir(path) == "." { + path = filepath.Join(pkgpath, "scripts", path) + } + + args = append([]string{path}, args...) + cmd, err := run("dojs", args...) + if err != nil { + return []byte{}, err + } + file, err := ioutil.ReadFile(outpath) + if err != nil { + // fmt.Println(cmd) + return cmd, err + } + cmd = append(cmd, file...) + // os.Remove(outpath) + return cmd, err } func Wait(msg string) { @@ -66,7 +88,6 @@ func Wait(msg string) { func run(name string, args ...string) ([]byte, error) { var ext string var out bytes.Buffer - var stderr bytes.Buffer switch runtime.GOOS { case "windows": @@ -77,10 +98,23 @@ func run(name string, args ...string) ([]byte, error) { if !strings.HasSuffix(name, ext) { name += ext } - args = append([]string{Opts, path.Join(pkgpath, "scripts", name)}, args...) + + if strings.Contains(name, "dojs") { + args = append([]string{Opts, filepath.Join(pkgpath, "scripts", name)}, + args[0], + fmt.Sprintf("%s", strings.Join(args[1:], ",")), + ) + } else { + args = append([]string{Opts, filepath.Join(pkgpath, "scripts", name)}, args...) + } + cmd := exec.Command(Cmd, args...) cmd.Stdout = &out - cmd.Stderr = &stderr + cmd.Stderr = &out err := cmd.Run() - return out.Bytes(), err + if err != nil { + return []byte{}, errors.New(string(out.Bytes())) + } else { + return out.Bytes(), err + } } diff --git a/ps_test.go b/ps_test.go index d412dd5..e6ca08b 100644 --- a/ps_test.go +++ b/ps_test.go @@ -2,7 +2,6 @@ package ps import ( "fmt" - _ "io/ioutil" "os" "path/filepath" _ "strings" @@ -20,24 +19,24 @@ func TestOpen(t *testing.T) { if testing.Short() { t.Skip("Skipping \"TestOpen\"") } - Open("F:\\GitLab\\dreamkeepers-psd\\Template009.1.psd") + _, err := Open("F:\\GitLab\\dreamkeepers-psd\\Template009.1.psd") + if err != nil { + t.Fatal(err) + } } -// TODO: Comparison borked -/*func TestRun(t *testing.T) { - out := []byte("Testing...\n") - msg, err := run("test") +func TestRun(t *testing.T) { + out := []byte("hello,\r\nworld!\r\n") + msg, err := run("test", "hello,", "world!") if err != nil { t.Fatal(err) } - if string(msg) == string(out) { - fail := fmt.Sprintf("run(test)\ngot:\t\"%s\"\nwant:\t\"%s\"\n", msg, out) + if string(msg) != string(out) { + fail := fmt.Sprintf("TestRun faild.\ngot:\n\"%s\"\nwant:\n\"%s\"\n", msg, out) t.Fatal(fail) - } else { - os.Remove(path.Join(pkgpath, "scripts", "test.txt")) } } -*/ + func TestQuit(t *testing.T) { if testing.Short() { t.Skip("Skipping \"TestQuit\"") @@ -50,21 +49,15 @@ func TestWait(t *testing.T) { fmt.Println() } -// TODO: Comparison borked -/*func TestJS(t *testing.T) { - out := "Testing...\n" - _, err := Js(path.Join(Folder, "test.jsx"), Folder) +func TestJS(t *testing.T) { + out := []byte("F:\\TEMP\\js_out.txt\r\narg\r\nargs\r\n") + script := "test.jsx" + ret, err := Js(script, "arg", "args") if err != nil { t.Fatal(err) } - f, err := ioutil.ReadFile(path.Join(Folder, "test.txt")) - if err != nil { - t.Fatal(err) - } - if strings.Compare(string(f), string(out)) != 0 { - fmt.Println(f) - fmt.Println([]byte(out)) - fail := fmt.Sprintf("TestJS failed.\ngot:\t\"%s\"\nwant:\t\"%s\"", f, out) + if string(ret) != string(out) { + fail := fmt.Sprintf("TestJS failed.\ngot:\t\"%s\"\nwant:\t\"%s\"", ret, out) t.Fatal(fail) } -}*/ +} diff --git a/scripts/dojs.vbs b/scripts/dojs.vbs index 222155b..6819bfd 100644 --- a/scripts/dojs.vbs +++ b/scripts/dojs.vbs @@ -1,10 +1,10 @@ -Dim app -Set app = CreateObject("Photoshop.Application") -if WScript.Arguments.Count = 0 then - WScript.Echo "Missing parameters" +Dim appRef +Set appRef = CreateObject("Photoshop.Application") +if wScript.Arguments.Count = 0 then + wScript.Echo "Missing parameters" else path = wScript.Arguments(0) - folder = wScript.Arguments(1) - app.DoJavaScriptFile path, Array(Folder) + args = wScript.Arguments(1) + appRef.DoJavaScriptFile path, Split(args, ",") end if \ No newline at end of file diff --git a/scripts/test.jsx b/scripts/test.jsx index 98d79ac..bc0c818 100644 --- a/scripts/test.jsx +++ b/scripts/test.jsx @@ -1,11 +1,10 @@ -var Path = arguments[0]; -alert(Path) -var saveFile = File(Path + "/test.txt"); - +var saveFile = File(arguments[0]); if(saveFile.exists) saveFile.remove(); saveFile.encoding = "UTF8"; saveFile.open("e", "TEXT", "????"); -saveFile.writeln("Testing..."); +for (var i = 0; i < arguments.length; i++) { + saveFile.writeln(arguments[i]) +} saveFile.close(); \ No newline at end of file diff --git a/scripts/test.vbs b/scripts/test.vbs index 12d7bf1..b413227 100644 --- a/scripts/test.vbs +++ b/scripts/test.vbs @@ -1 +1,3 @@ -wScript.echo "Testing..." \ No newline at end of file +for i=0 to wScript.Arguments.length-1 + wScript.echo wScript.Arguments(i) +next \ No newline at end of file From 36ccc2624393d472a63e09dcdb9c043a4b218706 Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 5 Feb 2018 21:28:43 -0500 Subject: [PATCH 07/22] documentation updates --- ps.go | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/ps.go b/ps.go index ef271f1..e0bd288 100644 --- a/ps.go +++ b/ps.go @@ -1,7 +1,7 @@ -// Package ps lets you manipulate Adobe Photoshop (CS5) from go. +// Package ps creates an interface between Adobe Photoshop (CS5) and go. // This is primarily done by calling VBS/Applescript files. // -// Currently only works on windows +// Currently only works on windows. package ps import ( @@ -39,8 +39,9 @@ func Start() error { } // Open a file. -func Open(path string) ([]byte, error) { - return run("open", path) +func Open(path string) error { + _, err := run("open", path) + return err } // Close the active document. @@ -49,9 +50,13 @@ func Close() error { return err } -// Quit photoshop with save status. -func Quit(save int) ([]byte, error) { - return run("quit", string(save)) +// Quits photoshop. +// +// There are 3 valid values for save: 1 (psSaveChanges), 2 (psDoNotSaveChanges), +// 3 (psPromptToSaveChanges). +func Quit(save int) error { + _, err := run("quit", string(save)) + return err } func Js(path string, args ...string) ([]byte, error) { @@ -79,6 +84,11 @@ func Js(path string, args ...string) ([]byte, error) { return cmd, err } +// Wait provides the user a message, and halts operation until the user +// signals that they are ready (by pushing enter). +// +// Useful for when you need to do something by hand in the middle of an +// automated process. func Wait(msg string) { fmt.Print(msg) var input string From 9cbf0e9b92cd2a3186ce92f112227cef7e808284 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 8 Mar 2018 11:38:00 -0500 Subject: [PATCH 08/22] Added functionality lib - Save - DoAction - ApplyDataset - GetLayer(s) main - do actions from commandline Improved test cases! --- cmd/ps/main.go | 27 ++++++++++++ ps.go | 88 ++++++++++++++++++++++++++++--------- ps_test.go | 93 ++++++++++++++++++++++++++++++++++------ scripts/action.vbs | 10 +++++ scripts/applyDataset.jsx | 15 +++++++ scripts/getLayers.jsx | 32 ++++++++++++++ scripts/save.vbs | 12 ++++++ scripts/skirmish.jsx | 72 +++++++++++++++++++++++++++++++ structs.go | 20 +++++++++ 9 files changed, 337 insertions(+), 32 deletions(-) create mode 100644 cmd/ps/main.go create mode 100644 scripts/action.vbs create mode 100644 scripts/applyDataset.jsx create mode 100644 scripts/getLayers.jsx create mode 100644 scripts/save.vbs create mode 100644 scripts/skirmish.jsx create mode 100644 structs.go diff --git a/cmd/ps/main.go b/cmd/ps/main.go new file mode 100644 index 0000000..b1d0fa9 --- /dev/null +++ b/cmd/ps/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "github.com/sbrow/ps" + "os" +) + +func main() { + args := []string{} + cmd := "" + switch { + case len(os.Args) > 1: + args = os.Args[2:] + fallthrough + case len(os.Args) > 0: + cmd = os.Args[1] + } + + fmt.Println(os.Args, cmd, args) + if cmd == "action" { + err := ps.DoAction(args[0], args[1]) + if err != nil { + panic(err) + } + } +} diff --git a/ps.go b/ps.go index e0bd288..4db93a1 100644 --- a/ps.go +++ b/ps.go @@ -1,11 +1,14 @@ -// Package ps creates an interface between Adobe Photoshop (CS5) and go. -// This is primarily done by calling VBS/Applescript files. +// Package ps is a rudimentary API between Adobe Photoshop and go. // -// Currently only works on windows. +// Most of the interaction between the two is implemented via +// javascript and/or VBS/Applescript. +// +// Currently only works with CS5 on Windows. package ps import ( "bytes" + "encoding/json" "errors" "fmt" "io/ioutil" @@ -32,25 +35,25 @@ func init() { } } -// Open photoshop. +// Start opens Photoshop. func Start() error { _, err := run("start") return err } -// Open a file. -func Open(path string) error { - _, err := run("open", path) +// Close closes the active document. +func Close() error { + _, err := run("close") return err } -// Close the active document. -func Close() error { - _, err := run("close") +// Open opens a file with the specified path. +func Open(path string) error { + _, err := run("open", path) return err } -// Quits photoshop. +// Quit exits Photoshop. // // There are 3 valid values for save: 1 (psSaveChanges), 2 (psDoNotSaveChanges), // 3 (psPromptToSaveChanges). @@ -59,9 +62,14 @@ func Quit(save int) error { return err } -func Js(path string, args ...string) ([]byte, error) { +// DoJs runs a Photoshop javascript script file (.jsx) from the specified location. +// It can't directly return output, so instead the scripts write their output to +// a temporary file. +func DoJs(path string, args ...string) ([]byte, error) { // Temp file for js to output to. outpath := filepath.Join(os.Getenv("TEMP"), "js_out.txt") + defer os.Remove(outpath) + args = append([]string{outpath}, args...) // If passed a script by name, assume it's in the default folder. @@ -76,28 +84,30 @@ func Js(path string, args ...string) ([]byte, error) { } file, err := ioutil.ReadFile(outpath) if err != nil { - // fmt.Println(cmd) return cmd, err } cmd = append(cmd, file...) - // os.Remove(outpath) return cmd, err } -// Wait provides the user a message, and halts operation until the user +// Wait prints a message to the console and halts operation until the user // signals that they are ready (by pushing enter). // // Useful for when you need to do something by hand in the middle of an -// automated process. +// otherwise automated process. func Wait(msg string) { + fmt.Println() fmt.Print(msg) var input string fmt.Scanln(&input) + fmt.Println() } +// run handles running the script files, returning output, and displaying errors. func run(name string, args ...string) ([]byte, error) { var ext string var out bytes.Buffer + var errs bytes.Buffer switch runtime.GOOS { case "windows": @@ -117,14 +127,52 @@ func run(name string, args ...string) ([]byte, error) { } else { args = append([]string{Opts, filepath.Join(pkgpath, "scripts", name)}, args...) } - cmd := exec.Command(Cmd, args...) cmd.Stdout = &out - cmd.Stderr = &out + cmd.Stderr = &errs err := cmd.Run() if err != nil { - return []byte{}, errors.New(string(out.Bytes())) - } else { return out.Bytes(), err + // return append(out.Bytes(), errs.Bytes()...), err + } + if len(errs.Bytes()) != 0 { + return out.Bytes(), errors.New(string(errs.Bytes())) } + return out.Bytes(), nil +} + +// DoAction runs a Photoshop action with name from set. +func DoAction(set, name string) error { + _, err := run("action", set, name) + return err +} + +// SaveAs saves the Photoshop document file to the given location. +func SaveAs(path string) error { + _, err := run("save", path) + return err +} + +// Layers returns an array of ArtLayers from the active document +// based on the given path string. +func Layers(path string) ([]ArtLayer, error) { + byt, err := DoJs("getLayers.jsx", path) + var out []ArtLayer + err = json.Unmarshal(byt, &out) + if err != nil { + return []ArtLayer{}, err + } + return out, err +} + +// Layer returns an ArtLayer from the active document given a specified +// path string. Layer calls Layers() and returns the first result. +func Layer(path string) (ArtLayer, error) { + lyrs, err := Layers(path) + return lyrs[0], err +} + +// ApplyDataset fills out a template file with information from a given dataset (csv) file. +func ApplyDataset(name string) ([]byte, error) { + return DoJs("applyDataset.jsx", name) } diff --git a/ps_test.go b/ps_test.go index e6ca08b..8bcae3c 100644 --- a/ps_test.go +++ b/ps_test.go @@ -2,6 +2,7 @@ package ps import ( "fmt" + // "log" "os" "path/filepath" _ "strings" @@ -15,14 +16,42 @@ func TestPkgPath(t *testing.T) { } } +func TestStart(t *testing.T) { + Start() +} + func TestOpen(t *testing.T) { if testing.Short() { t.Skip("Skipping \"TestOpen\"") } - _, err := Open("F:\\GitLab\\dreamkeepers-psd\\Template009.1.psd") + err := Open("F:\\GitLab\\dreamkeepers-psd\\Template009.1.psd") + if err != nil { + t.Fatal(err) + } +} + +func TestClose(t *testing.T) { + Close() +} + +func TestQuit(t *testing.T) { + if testing.Short() { + t.Skip("Skipping \"TestQuit\"") + } + Quit(2) +} + +func TestDoJs(t *testing.T) { + out := []byte("F:\\TEMP\\js_out.txt\r\narg\r\nargs\r\n") + script := "test.jsx" + ret, err := DoJs(script, "arg", "args") if err != nil { t.Fatal(err) } + if string(ret) != string(out) { + fail := fmt.Sprintf("TestJS failed.\ngot:\t\"%s\"\nwant:\t\"%s\"", ret, out) + t.Fatal(fail) + } } func TestRun(t *testing.T) { @@ -37,22 +66,62 @@ func TestRun(t *testing.T) { } } -func TestQuit(t *testing.T) { - if testing.Short() { - t.Skip("Skipping \"TestQuit\"") +func TestWait(t *testing.T) { + Wait("Waiting...") +} + +func TestDoAction_Crop(t *testing.T) { + err := Open("F:\\GitLab\\dreamkeepers-psd\\Template009.1.psd") + if err != nil { + t.Fatal(err) + } + err = DoAction("DK", "Crop") + if err != nil { + t.Fatal(err) } - Quit(2) } -func TestWait(t *testing.T) { - Wait("Waiting...") - fmt.Println() +func TestDoAction_Undo(t *testing.T) { + err := DoAction("DK", "Undo") + if err != nil { + t.Fatal(err) + } } -func TestJS(t *testing.T) { - out := []byte("F:\\TEMP\\js_out.txt\r\narg\r\nargs\r\n") - script := "test.jsx" - ret, err := Js(script, "arg", "args") +func TestSaveAs(t *testing.T) { + err := SaveAs("F:\\TEMP\\test.png") + if err != nil { + t.Fatal(err) + } +} + +func TestLayers(t *testing.T) { + byt, err := Layers("Areas/TitleBackground") + // _, err := Layers("Areas/TitleBackground") + if err != nil { + t.Fatal(err) + } + for _, lyr := range byt { + fmt.Println(lyr.Name) + fmt.Println(lyr.Bounds) + } + +} + +func TestLayer(t *testing.T) { + // lyr, err := Layer("Areas/TitleBackground") + _, err := Layer("Areas/TitleBackground") + if err != nil { + t.Fatal(err) + } + /* fmt.Println(lyr.Name) + fmt.Println(lyr.Bounds) + */ +} + +func TestApplyDataset(t *testing.T) { + out := []byte("done!\r\n") + ret, err := ApplyDataset("Anger") if err != nil { t.Fatal(err) } diff --git a/scripts/action.vbs b/scripts/action.vbs new file mode 100644 index 0000000..231e2ce --- /dev/null +++ b/scripts/action.vbs @@ -0,0 +1,10 @@ +set appRef = CreateObject("Photoshop.Application") +' No dialogs' +dlgMode = 3 + +set desc = CreateObject( "Photoshop.ActionDescriptor" ) +set ref = CreateObject( "Photoshop.ActionReference" ) +Call ref.PutName(appRef.CharIDToTypeID("Actn"), wScript.Arguments(1)) +Call ref.PutName(appRef.CharIDToTypeID("ASet"), wScript.Arguments(0)) +Call desc.PutReference(appRef.CharIDToTypeID("null"), ref) +Call appRef.ExecuteAction(appRef.CharIDToTypeID("Ply "), desc, dlgMode) \ No newline at end of file diff --git a/scripts/applyDataset.jsx b/scripts/applyDataset.jsx new file mode 100644 index 0000000..2e35e13 --- /dev/null +++ b/scripts/applyDataset.jsx @@ -0,0 +1,15 @@ +var saveFile = File(arguments[0]); +if(saveFile.exists) + saveFile.remove(); +var idAply = charIDToTypeID("Aply"); +var desc1 = new ActionDescriptor(); +var idnull = charIDToTypeID("null"); +var ref1 = new ActionReference(); +var iddataSetClass = stringIDToTypeID("dataSetClass"); +ref1.putName(iddataSetClass, arguments[1]); +desc1.putReference(idnull, ref1); +executeAction(idAply, desc1, DialogModes.NO); +saveFile.encoding = "UTF8"; +saveFile.open("e", "TEXT", "????"); +saveFile.writeln("done!"); +saveFile.close(); \ No newline at end of file diff --git a/scripts/getLayers.jsx b/scripts/getLayers.jsx new file mode 100644 index 0000000..b58a834 --- /dev/null +++ b/scripts/getLayers.jsx @@ -0,0 +1,32 @@ +//arguments = [ 'F:\\TEMP\\js_out.txt\r\narg\r\nargs\r\n', 'Areas/TitleBackground'] +var saveFile = File(arguments[0]) +if(saveFile.exists) + saveFile.remove() +saveFile.encoding = "UTF8" +saveFile.open("e", "TEXT", "????") +try { + var doc = app.activeDocument + var splitPath = arguments[1].split('/') + var bottomLayerSet = doc.layerSets.getByName(splitPath[0]) + for (var i = 1; i < splitPath.length; i++) { + try {bottomLayerSet = bottomLayerSet.layerSets.getByName(splitPath[i])} + catch (e) {bottomLayerSet = { layers: [bottomLayerSet.layers.getByName(splitPath[i])] }} + } + saveFile.writeln('['); + for (var l = 0; l < bottomLayerSet.layers.length; l++) { + var lyr = bottomLayerSet.layers[l] + saveFile.write('{"Name":"' + lyr.name + '", "Bounds": [["' + lyr.bounds[0] + '","' + + lyr.bounds[1] + '","' + lyr.bounds[2] + '"],["' + lyr.bounds[3] + '"]]}'); + if (l != bottomLayerSet.layers.length - 1) + saveFile.write(','); + saveFile.writeln(); + + } + saveFile.writeln(']'); +} catch (e) { + if (e.message.indexOf('User') == -1) + alert('ERROR: ' + e.message + ' at ' + e.fileName + ':' + e.line); + else + throw new Exception('User cancelled the operation'); +} +saveFile.close(); \ No newline at end of file diff --git a/scripts/save.vbs b/scripts/save.vbs new file mode 100644 index 0000000..e23f1ac --- /dev/null +++ b/scripts/save.vbs @@ -0,0 +1,12 @@ +Set appRef = CreateObject("Photoshop.Application") +dlgMode = 3 'No dialog +set d = CreateObject( "Photoshop.ActionDescriptor" ) +Call d.PutEnumerated(appRef.CharIDToTypeID("PGIT"), appRef.CharIDToTypeID("PGIT"), appRef.CharIDToTypeID("PGIN")) +Call d.PutEnumerated(appRef.CharIDToTypeID("PNGf"), appRef.CharIDToTypeID("PNGf"), appRef.CharIDToTypeID("PGAd")) + +SET desc = CreateObject( "Photoshop.ActionDescriptor" ) +Call desc.PutObject( appRef.CharIDToTypeID("As "), appRef.CharIDToTypeID("PNGF"), d) +Call desc.PutPath( appRef.CharIDToTypeID("In "), wScript.Arguments(0)) +Call desc.PutBoolean( appRef.CharIDToTypeID("Cpy "), True ) + +Call appRef.ExecuteAction(appRef.CharIDToTypeID("save"), desc, dlgMode) \ No newline at end of file diff --git a/scripts/skirmish.jsx b/scripts/skirmish.jsx new file mode 100644 index 0000000..2f4b643 --- /dev/null +++ b/scripts/skirmish.jsx @@ -0,0 +1,72 @@ +function setTitle(title) { + var nameLayer = this.textLayers.getByName('name'); + var found = false; + for (var i = 0; i < this.titleBackgrounds.length; i++) { + if (!found && (nameLayer.bounds[2] + this.tolerance.title) < this.titleBackgrounds[i].bounds[2]) { + this.log.log('"{0}" is long enough'.format(this.titleBackgrounds[i].name), '-'); + this.titleBackgrounds[i].visible = true; + found = true; + } else { + this.log.log('"{0}" is too short'.format(this.titleBackgrounds[i].name),'-') + this.titleBackgrounds[i].visible = false; + } + } + +} + +function main() { + setTitle() + if ((this.Type).indexOf("Channel") != -2) { + this.changeColor(this.resolveBanner.normal, this.colors.Rarity); + } else { + this.changeColor(this.resolveBanner.normal, [128, 128, 128]); + } + formatText() +} + +DeckCardPSD.prototype.formatText = function() { + var speed = this.textLayers.getByName('speed'); + if (speed.visible) { + this.changeStroke(speed, (speed.textItem.contents == 1) ? [128, 128, 128] : [255, 255, 255], + this.colors.banner) + } + /** + * The lowest we allow a text layer to go. + * @type {int} + */ + var bottom = this.doc.height-this.tolerance.flavor_text + + // Get our text layers. + var short_text = this.setTextLayer('short_text', undefined, null, 'Arial', 'Regular',[this.bold_words, "Bold"]); + var long_text = this.textLayers.getByName('long_text'); + var flavor_text = this.textLayers.getByName('flavor_text'); + + // Position the layers. + positionLayer(this.short_textBackground, this.short_textBackground.bounds[0], short_text.bounds[3] + this.tolerance.short_text, 'bottom'); + positionLayer(long_text, long_text.bounds[0], this.short_textBackground.bounds[3] + this.tolerance.long_text, 'top'); + positionLayer(flavor_text, flavor_text.bounds[0], bottom, 'bottom'); + + /** + * Make our layers visible + * @todo hack, fix. + */ + short_text.visible = short_text.textItem.contents != "“"; + long_text.visible = long_text.textItem.contents != "“"; + flavor_text.visible = flavor_text.textItem.contents != "“"; + + + + //Hide long_text if too long. + if (long_text.bounds[3] > this.doc.height - bottom) { + long_text.visible == false; + } + + this.log.debug(short_text.bounds) + this.log.debug(long_text.bounds) + this.log.debug(flavor_text.bounds) + //Hide flavor text if too long. + if ( (long_text.visible && flavor_text.bounds[1] < long_text.bounds[3]) + || (short_text.visible && flavor_text.bounds[1] < short_text.bounds[3])) { + flavor_text.visible = false; + } +}; \ No newline at end of file diff --git a/structs.go b/structs.go new file mode 100644 index 0000000..9d38402 --- /dev/null +++ b/structs.go @@ -0,0 +1,20 @@ +package ps + +// type layer interface { +// Name() string +// TextItem() []string +// } + +type ArtLayer struct { + Name string + TextItem string + Bounds [2][2]string +} + +// func (a *ArtLayer) Name() string { +// return a.name +// } + +// func (a *ArtLayer) TextItem() string { +// return a.textItem +// } From 17a7f8ccd17d99280ed47a98122707a26e428d0a Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 8 Mar 2018 14:24:22 -0500 Subject: [PATCH 09/22] Fixed - close() / quit() Added functionality - setlayervisibility() Made SaveOptions into an enum for better readibility --- ps.go | 25 ++++++++++++------ ps_test.go | 41 ++++++++++++++++++++++-------- scripts/close.vbs | 2 +- scripts/getLayers.jsx | 46 ++++++++++++---------------------- scripts/lib.js | 34 +++++++++++++++++++++++++ scripts/quit.vbs | 2 +- scripts/setLayerVisibility.jsx | 11 ++++++++ structs.go | 7 +++++- 8 files changed, 117 insertions(+), 51 deletions(-) create mode 100644 scripts/lib.js create mode 100644 scripts/setLayerVisibility.jsx diff --git a/ps.go b/ps.go index 4db93a1..d46d3a2 100644 --- a/ps.go +++ b/ps.go @@ -23,6 +23,19 @@ var Cmd string var Opts string var pkgpath string +// PSSaveOptions is an enum for options when closing a document. +type PSSaveOptions int + +func (p *PSSaveOptions) String() string { + return fmt.Sprint("", *p) +} + +const ( + PSSaveChanges PSSaveOptions = 1 + PSDoNotSaveChanges PSSaveOptions = 2 + PSPromptToSaveChanges PSSaveOptions = 3 +) + func init() { _, file, _, _ := runtime.Caller(0) pkgpath = filepath.Dir(file) @@ -42,8 +55,8 @@ func Start() error { } // Close closes the active document. -func Close() error { - _, err := run("close") +func Close(save PSSaveOptions) error { + _, err := run("close", save.String()) return err } @@ -54,11 +67,8 @@ func Open(path string) error { } // Quit exits Photoshop. -// -// There are 3 valid values for save: 1 (psSaveChanges), 2 (psDoNotSaveChanges), -// 3 (psPromptToSaveChanges). -func Quit(save int) error { - _, err := run("quit", string(save)) +func Quit(save PSSaveOptions) error { + _, err := run("quit", save.String()) return err } @@ -96,7 +106,6 @@ func DoJs(path string, args ...string) ([]byte, error) { // Useful for when you need to do something by hand in the middle of an // otherwise automated process. func Wait(msg string) { - fmt.Println() fmt.Print(msg) var input string fmt.Scanln(&input) diff --git a/ps_test.go b/ps_test.go index 8bcae3c..3a9a104 100644 --- a/ps_test.go +++ b/ps_test.go @@ -2,10 +2,8 @@ package ps import ( "fmt" - // "log" "os" "path/filepath" - _ "strings" "testing" ) @@ -17,7 +15,10 @@ func TestPkgPath(t *testing.T) { } func TestStart(t *testing.T) { - Start() + err := Start() + if err != nil { + t.Fatal(err) + } } func TestOpen(t *testing.T) { @@ -31,14 +32,23 @@ func TestOpen(t *testing.T) { } func TestClose(t *testing.T) { - Close() + if testing.Short() { + t.Skip("Skipping \"TestClose\"") + } + err := Close(2) + if err != nil { + t.Fatal(err) + } } func TestQuit(t *testing.T) { if testing.Short() { t.Skip("Skipping \"TestQuit\"") } - Quit(2) + err := Quit(2) + if err != nil { + t.Fatal(err) + } } func TestDoJs(t *testing.T) { @@ -109,14 +119,14 @@ func TestLayers(t *testing.T) { } func TestLayer(t *testing.T) { - // lyr, err := Layer("Areas/TitleBackground") - _, err := Layer("Areas/TitleBackground") + lyr, err := Layer("Areas/TitleBackground") + // _, err := Layer("Areas/TitleBackground") if err != nil { t.Fatal(err) } - /* fmt.Println(lyr.Name) - fmt.Println(lyr.Bounds) - */ + fmt.Println(lyr.Name) + fmt.Println(lyr.Bounds) + } func TestApplyDataset(t *testing.T) { @@ -129,4 +139,15 @@ func TestApplyDataset(t *testing.T) { fail := fmt.Sprintf("TestJS failed.\ngot:\t\"%s\"\nwant:\t\"%s\"", ret, out) t.Fatal(fail) } + err = Quit(2) + if err != nil { + t.Fatal(err) + } +} + +func TestDoJs_HideLayer(t *testing.T) { + _, err := DoJs("hideLayers.jsx", "Areas/TitleBackground") + if err != nil { + t.Fatal(err) + } } diff --git a/scripts/close.vbs b/scripts/close.vbs index 1e18426..ac2275f 100644 --- a/scripts/close.vbs +++ b/scripts/close.vbs @@ -1,3 +1,3 @@ set App = CreateObject("Photoshop.Application") set Doc = App.activeDocument -Doc.Close \ No newline at end of file +Doc.Close(CInt(wScript.Arguments(0))) \ No newline at end of file diff --git a/scripts/getLayers.jsx b/scripts/getLayers.jsx index b58a834..fafd93d 100644 --- a/scripts/getLayers.jsx +++ b/scripts/getLayers.jsx @@ -1,32 +1,18 @@ -//arguments = [ 'F:\\TEMP\\js_out.txt\r\narg\r\nargs\r\n', 'Areas/TitleBackground'] -var saveFile = File(arguments[0]) -if(saveFile.exists) - saveFile.remove() -saveFile.encoding = "UTF8" -saveFile.open("e", "TEXT", "????") -try { - var doc = app.activeDocument - var splitPath = arguments[1].split('/') - var bottomLayerSet = doc.layerSets.getByName(splitPath[0]) - for (var i = 1; i < splitPath.length; i++) { - try {bottomLayerSet = bottomLayerSet.layerSets.getByName(splitPath[i])} - catch (e) {bottomLayerSet = { layers: [bottomLayerSet.layers.getByName(splitPath[i])] }} - } - saveFile.writeln('['); - for (var l = 0; l < bottomLayerSet.layers.length; l++) { - var lyr = bottomLayerSet.layers[l] - saveFile.write('{"Name":"' + lyr.name + '", "Bounds": [["' + lyr.bounds[0] + '","' + - lyr.bounds[1] + '","' + lyr.bounds[2] + '"],["' + lyr.bounds[3] + '"]]}'); - if (l != bottomLayerSet.layers.length - 1) - saveFile.write(','); - saveFile.writeln(); +#include lib.js +var stdout = newFile(arguments[0]) +var set = getLayers(arguments[1]) + +stdout.writeln('['); +for (var l = 0; l < set.layers.length; l++) { + var lyr = set.layers[l] + var lyrset = arguments[1].replace(lyr.name, "") + stdout.write(('{"Name":"' + lyr.name + '", "Bounds": [[' + lyr.bounds[0] + ',' + + lyr.bounds[1] + '],[' + lyr.bounds[2] + ',' + + lyr.bounds[3] + ']], "LayerSet": "' + lyrset + '"}').replace(/ px/g, "")); + if (l != set.layers.length - 1) + stdout.write(','); + stdout.writeln(); - } - saveFile.writeln(']'); -} catch (e) { - if (e.message.indexOf('User') == -1) - alert('ERROR: ' + e.message + ' at ' + e.fileName + ':' + e.line); - else - throw new Exception('User cancelled the operation'); } -saveFile.close(); \ No newline at end of file +stdout.writeln(']'); +stdout.close() \ No newline at end of file diff --git a/scripts/lib.js b/scripts/lib.js new file mode 100644 index 0000000..248dda4 --- /dev/null +++ b/scripts/lib.js @@ -0,0 +1,34 @@ +// Opens and returns a file, overwriting new data. +function newFile(path) { + var f = File(path) + if(f.exists) + f.remove() + f.encoding = "UTF8" + f.open("e", "TEXT", "????") + return f +} + +// Returns an array of ArtLayers from a layerSet or an ArtLayer. +function getLayers(path) { + try { + var doc = app.activeDocument + var path = path.split('/') + var lyrs = doc.layerSets.getByName(path[0]) + for (var i = 1; i < path.length; i++) { + try { + lyrs = lyrs.layerSets.getByName(path[i]) + } catch (e) { + lyrs = { layers: [lyrs.layers.getByName(path[i])] } } + } + return lyrs + } catch (e) { + if (e.message.indexOf('User') == -1) + alert(err(e)); + else + throw new Exception('User cancelled the operation'); + } +} + +function err(e) { + return 'ERROR: ' + e.message + ' at ' + e.fileName + ':' + e.line; +} \ No newline at end of file diff --git a/scripts/quit.vbs b/scripts/quit.vbs index b4dbca4..9f77998 100644 --- a/scripts/quit.vbs +++ b/scripts/quit.vbs @@ -2,7 +2,7 @@ Set appRef = CreateObject("Photoshop.Application") Do While appRef.Documents.Count > 0 - appRef.ActiveDocument.Close(wScript.Arguments(0)) + appRef.ActiveDocument.Close(CInt(wScript.Arguments(0))) Loop appRef.Quit() \ No newline at end of file diff --git a/scripts/setLayerVisibility.jsx b/scripts/setLayerVisibility.jsx new file mode 100644 index 0000000..ce52ae4 --- /dev/null +++ b/scripts/setLayerVisibility.jsx @@ -0,0 +1,11 @@ +#include lib.js +var stdout = newFile(arguments[0]) +var lyrs = getLayers(arguments[1]) +var vis = arguments[2] == "true" +try { + for (var i = 0; i < lyrs.layers.length; i++) + lyrs.layers[i].visible = vis +} catch (e) { + stdout.writeln(err(e)) +} +stdout.close() \ No newline at end of file diff --git a/structs.go b/structs.go index 9d38402..141111b 100644 --- a/structs.go +++ b/structs.go @@ -8,7 +8,12 @@ package ps type ArtLayer struct { Name string TextItem string - Bounds [2][2]string + Bounds [2][2]int + LayerSet string +} + +func (a *ArtLayer) SetVisible() { + DoJs("setLayerVisibility.jsx", a.LayerSet+"/"+a.Name, "true") } // func (a *ArtLayer) Name() string { From 7725110e353b45e422f8aad0baf1b5a78c454c14 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 18 Mar 2018 20:50:58 -0400 Subject: [PATCH 10/22] WIP --- ps.go | 31 ++++--- ps_test.go | 120 ++++++++++++++++++++++---- scripts/compilejs.jsx | 4 + scripts/getActiveDoc.jsx | 38 +++++++++ scripts/getActiveDocument.jsx | 50 +++++++++++ scripts/getLayer.jsx | 8 ++ scripts/getLayerSet.jsx | 15 ++++ scripts/getLayers.jsx | 18 ---- scripts/lib.js | 40 ++++----- scripts/moveLayer.jsx | 6 ++ scripts/setLayerVisibility.jsx | 11 --- scripts/skirmish.jsx | 31 ++----- structs.go | 149 +++++++++++++++++++++++++++++---- 13 files changed, 410 insertions(+), 111 deletions(-) create mode 100644 scripts/compilejs.jsx create mode 100644 scripts/getActiveDoc.jsx create mode 100644 scripts/getActiveDocument.jsx create mode 100644 scripts/getLayer.jsx create mode 100644 scripts/getLayerSet.jsx delete mode 100644 scripts/getLayers.jsx create mode 100644 scripts/moveLayer.jsx delete mode 100644 scripts/setLayerVisibility.jsx diff --git a/ps.go b/ps.go index d46d3a2..8ad1195 100644 --- a/ps.go +++ b/ps.go @@ -8,7 +8,6 @@ package ps import ( "bytes" - "encoding/json" "errors" "fmt" "io/ioutil" @@ -75,7 +74,7 @@ func Quit(save PSSaveOptions) error { // DoJs runs a Photoshop javascript script file (.jsx) from the specified location. // It can't directly return output, so instead the scripts write their output to // a temporary file. -func DoJs(path string, args ...string) ([]byte, error) { +func DoJs(path string, args ...string) (out []byte, err error) { // Temp file for js to output to. outpath := filepath.Join(os.Getenv("TEMP"), "js_out.txt") defer os.Remove(outpath) @@ -164,8 +163,8 @@ func SaveAs(path string) error { // Layers returns an array of ArtLayers from the active document // based on the given path string. -func Layers(path string) ([]ArtLayer, error) { - byt, err := DoJs("getLayers.jsx", path) +/*func Layers(path string) ([]ArtLayer, error) { + byt, err := DoJs("getLayers.jsx", JSLayer(path)) var out []ArtLayer err = json.Unmarshal(byt, &out) if err != nil { @@ -173,15 +172,25 @@ func Layers(path string) ([]ArtLayer, error) { } return out, err } - -// Layer returns an ArtLayer from the active document given a specified -// path string. Layer calls Layers() and returns the first result. -func Layer(path string) (ArtLayer, error) { - lyrs, err := Layers(path) - return lyrs[0], err -} +*/ // ApplyDataset fills out a template file with information from a given dataset (csv) file. func ApplyDataset(name string) ([]byte, error) { return DoJs("applyDataset.jsx", name) } + +// JSLayer retrurns javascript code to get the layer with a given path. +func JSLayer(path string) string { + pth := strings.Split(path, "/") + js := "app.activeDocument" + last := len(pth) - 1 + if last > 0 { + for i := 0; i < last; i++ { + js += fmt.Sprintf(".layerSets.getByName('%s')", pth[i]) + } + } + if pth[last] != "" { + js += fmt.Sprintf(".artLayers.getByName('%s')", pth[last]) + } + return js + ";" +} diff --git a/ps_test.go b/ps_test.go index 3a9a104..4c6ce68 100644 --- a/ps_test.go +++ b/ps_test.go @@ -21,6 +21,7 @@ func TestStart(t *testing.T) { } } +/* func TestOpen(t *testing.T) { if testing.Short() { t.Skip("Skipping \"TestOpen\"") @@ -30,7 +31,8 @@ func TestOpen(t *testing.T) { t.Fatal(err) } } - +*/ +/* func TestClose(t *testing.T) { if testing.Short() { t.Skip("Skipping \"TestClose\"") @@ -106,29 +108,80 @@ func TestSaveAs(t *testing.T) { } func TestLayers(t *testing.T) { - byt, err := Layers("Areas/TitleBackground") - // _, err := Layers("Areas/TitleBackground") + l, err := Layers("Areas/TitleBackground/") + fmt.Println(l) if err != nil { t.Fatal(err) } - for _, lyr := range byt { - fmt.Println(lyr.Name) - fmt.Println(lyr.Bounds) +} +*/ +/* +func TestLayer(t *testing.T) { + _, err := Layer("Border/Inner Border") + if err != nil { + t.Fatal(err) } +}*/ -} +/*func TestMove(t *testing.T) { + lyr, err := Layer("Group 1/Layer 1") + if err != nil { + t.Fatal(err) + } + lyr.Position(100, 50, "top") +}*/ -func TestLayer(t *testing.T) { - lyr, err := Layer("Areas/TitleBackground") - // _, err := Layer("Areas/TitleBackground") +/* +func TestLayerSet(t *testing.T) { + set, err := GetLayerSet("Indicators/") + fmt.Println(set) + fmt.Println(set.ArtLayers[0].Parent) + if err != nil { + t.Fatal(err) + } +} +*/ + +func TestDocument(t *testing.T) { + d, err := GetDocument() + fmt.Println(d) + fmt.Println(d.ArtLayers[0]) + fmt.Println(d.ArtLayers[0].Parent) + fmt.Println(d.LayerSets[0]) + fmt.Println(d.LayerSets[0].Parent) + fmt.Println(d.LayerSets[0].ArtLayers[0]) + fmt.Println(d.LayerSets[0].ArtLayers[0].Parent) + fmt.Println(d.LayerSets[0].ArtLayers[0].Parent.Parent()) if err != nil { t.Fatal(err) } - fmt.Println(lyr.Name) - fmt.Println(lyr.Bounds) + if d != d.ArtLayers[0].Parent { + t.Fatal("Fucked") + } + if d != d.LayerSets[0].Parent() { + t.Fatal("Fucked") + } + if d.LayerSets[0] != d.LayerSets[0].ArtLayers[0].Parent { + t.Fatal("Fucked") + } + +} +/*func TestActiveDocument(t *testing.T) { + e, err := DoJs("compilejs.jsx", "alert('testing!')") + fmt.Println(string(e)) + if err != nil { + t.Fatal(err) + } + doc, err := ActiveDocument() + fmt.Println(doc) + if err != nil { + t.Fatal(err) + } } +*/ +/* func TestApplyDataset(t *testing.T) { out := []byte("done!\r\n") ret, err := ApplyDataset("Anger") @@ -144,10 +197,49 @@ func TestApplyDataset(t *testing.T) { t.Fatal(err) } } +*/ -func TestDoJs_HideLayer(t *testing.T) { - _, err := DoJs("hideLayers.jsx", "Areas/TitleBackground") +/*func TestDoJs_HideLayer(t *testing.T) { + _, err := DoJs("setLayerVisibility.jsx", "Areas/TitleBackground", "false") if err != nil { t.Fatal(err) } +}*/ + +//.8s +//.15 +func BenchmarkHideLayer(b *testing.B) { + for i := 0; i < b.N; i++ { + // _, err := Layers("Areas/TitleBackground/") + // if err != nil { + // b.Fatal(err) + // } + } +} + +// 59ns +func BenchmarkHelloWorld_go(b *testing.B) { + for i := 0; i < b.N; i++ { + fmt.Sprintf("Hello, world!") + } +} + +// ~35200000ns (.0352s) +func BenchmarkHelloWorld_vbs(b *testing.B) { + for i := 0; i < b.N; i++ { + _, err := run("helloworld") + if err != nil { + b.Fatal(err) + } + } +} + +// ~51700000 (0.0517) +func BenchmarkHelloWorld_js(b *testing.B) { + for i := 0; i < b.N; i++ { + _, err := DoJs("test.jsx", "Hello, World!") + if err != nil { + b.Fatal(err) + } + } } diff --git a/scripts/compilejs.jsx b/scripts/compilejs.jsx new file mode 100644 index 0000000..e732956 --- /dev/null +++ b/scripts/compilejs.jsx @@ -0,0 +1,4 @@ +#include lib.js +var stdout = newFile(arguments[0]) +eval(arguments[1]); +stdout.close() \ No newline at end of file diff --git a/scripts/getActiveDoc.jsx b/scripts/getActiveDoc.jsx new file mode 100644 index 0000000..465d260 --- /dev/null +++ b/scripts/getActiveDoc.jsx @@ -0,0 +1,38 @@ +#include lib.js +var stdout = newFile(arguments[0]); +var doc = app.activeDocument; +stdout.writeln(('{"Name": "' + doc.name +'", "Height":' +doc.height + + ', "Width":' + doc.width + ', "ArtLayers": [').replace(/ px/g, "")); +function layers(lyrs) { + if (typeof lyrs === 'undefined') + return; + for (var i = 0; i < lyrs.length; i++) { + var lyr = lyrs[i]; + stdout.write(('{"Name":"' + lyr.name + '", "Bounds": [[' + lyr.bounds[0] + ',' + + lyr.bounds[1] + '],[' + lyr.bounds[2] + ',' + + lyr.bounds[3] + ']], "Visible": ' + lyr.visible + '}').replace(/ px/g, "")); + if (i+1 != lyrs.length) + stdout.write(','); + stdout.writeln(); + } +} +layers(doc.artLayers) +stdout.writeln('], "LayerSets": ['); +function lyrSets(sets, nm) { + if (typeof sets === 'undefined') + return; + for (var i = 0; i < sets.length; i++) { + var set = sets[i]; + var name = nm + set.name + "/"; + stdout.write('{"Name": "' + set.name + '", "LayerSets": ['); + // lyrSets(set.layerSets, name); + stdout.write('], "ArtLayers": ['); + layers(set.artLayers); + stdout.write(']}'); + if (i+1 != sets.length) + stdout.write(','); + } +} +lyrSets(doc.layerSets) +stdout.write(']}'); +stdout.close(); \ No newline at end of file diff --git a/scripts/getActiveDocument.jsx b/scripts/getActiveDocument.jsx new file mode 100644 index 0000000..9e2cb41 --- /dev/null +++ b/scripts/getActiveDocument.jsx @@ -0,0 +1,50 @@ +#include lib.js + +var stdout = newFile(arguments[0]); +var doc = app.activeDocument; + +stdout.write(('{"Name": "' + doc.name +'", "Height":' +doc.height + + ', "Width":' + doc.width + ", ").replace(/ px/g, "")); + +function layersNsets(obj) { + stdout.write('"ArtLayers": ['); + lyrss(obj.artLayers, "") + stdout.write('], "LayerSets": ['); + lyrSets(obj.layerSets, ""); + // stdout.write('], "LayerSets": ['); +} + +function lyrss(lyrs, set) { + if (typeof lyrs === 'undefined') + return; + for (var i = 0; i < lyrs.length; i++) { + var lyr = lyrs[i]; + stdout.write(('{"Name":"' + lyr.name + '", "Bounds": [[' + lyr.bounds[0] + ',' + + lyr.bounds[1] + '],[' + lyr.bounds[2] + ',' + + lyr.bounds[3] + ']], "Path": "' + set + + '", "Visible": ' + lyr.visible + '}').replace(/ px/g, "")); + if (i+1 != lyrs.length) + stdout.write(','); + } +} +function lyrSets(sets, nm) { + if (typeof sets === 'undefined') + return; + for (var i = 0; i < sets.length; i++) { + var set = sets[i]; + var name = nm + set.name + "/"; + stdout.write('{"Name": "' + set.name + '", "LayerSets": ['); + lyrSets(set.layerSets, name); + stdout.write('], "Layers": ['); + lyrss(set.artLayers, name); + stdout.write(']}'); + if (i+1 != sets.length) + stdout.write(','); + } +} + +layersNsets(doc) +stdout.writeln(']}'); + +alert(doc.layerSets.getByName("Group 2").layerSets.getByName("Group 1").layers.getByName("Layer 1").name) +stdout.close(); \ No newline at end of file diff --git a/scripts/getLayer.jsx b/scripts/getLayer.jsx new file mode 100644 index 0000000..c42144a --- /dev/null +++ b/scripts/getLayer.jsx @@ -0,0 +1,8 @@ +#include lib.js + +var stdout = newFile(arguments[0]); +var lyr = eval(arguments[1]); +stdout.writeln(('{"Name":"' + lyr.name + '", "Bounds": [[' + lyr.bounds[0] + ',' + + lyr.bounds[1] + '],[' + lyr.bounds[2] + ',' + + lyr.bounds[3] + ']], "Visible": ' + lyr.visible + '}').replace(/ px/g, "")); +stdout.close(); \ No newline at end of file diff --git a/scripts/getLayerSet.jsx b/scripts/getLayerSet.jsx new file mode 100644 index 0000000..8e7b361 --- /dev/null +++ b/scripts/getLayerSet.jsx @@ -0,0 +1,15 @@ +#include lib.js + +var stdout = newFile(arguments[0]); +var set = eval(arguments[1]); +stdout.writeln('{"Name": "'+set.name+'", "ArtLayers":['); +for (var i = 0; i < set.artLayers.length; i++) { + var lyr = set.artLayers[i]; + stdout.write(('{"Name":"' + lyr.name + '", "Bounds": [[' + lyr.bounds[0] + ',' + + lyr.bounds[1] + '],[' + lyr.bounds[2] + ',' + + lyr.bounds[3] + ']], "Visible": ' + lyr.visible + '}').replace(/ px/g, "")); + if (i != set.artLayers.length - 1) + stdout.writeln(","); +} +stdout.write("]}") +stdout.close(); \ No newline at end of file diff --git a/scripts/getLayers.jsx b/scripts/getLayers.jsx deleted file mode 100644 index fafd93d..0000000 --- a/scripts/getLayers.jsx +++ /dev/null @@ -1,18 +0,0 @@ -#include lib.js -var stdout = newFile(arguments[0]) -var set = getLayers(arguments[1]) - -stdout.writeln('['); -for (var l = 0; l < set.layers.length; l++) { - var lyr = set.layers[l] - var lyrset = arguments[1].replace(lyr.name, "") - stdout.write(('{"Name":"' + lyr.name + '", "Bounds": [[' + lyr.bounds[0] + ',' + - lyr.bounds[1] + '],[' + lyr.bounds[2] + ',' + - lyr.bounds[3] + ']], "LayerSet": "' + lyrset + '"}').replace(/ px/g, "")); - if (l != set.layers.length - 1) - stdout.write(','); - stdout.writeln(); - -} -stdout.writeln(']'); -stdout.close() \ No newline at end of file diff --git a/scripts/lib.js b/scripts/lib.js index 248dda4..7046b59 100644 --- a/scripts/lib.js +++ b/scripts/lib.js @@ -8,27 +8,27 @@ function newFile(path) { return f } -// Returns an array of ArtLayers from a layerSet or an ArtLayer. -function getLayers(path) { - try { - var doc = app.activeDocument - var path = path.split('/') - var lyrs = doc.layerSets.getByName(path[0]) - for (var i = 1; i < path.length; i++) { - try { - lyrs = lyrs.layerSets.getByName(path[i]) - } catch (e) { - lyrs = { layers: [lyrs.layers.getByName(path[i])] } } - } - return lyrs - } catch (e) { - if (e.message.indexOf('User') == -1) - alert(err(e)); - else - throw new Exception('User cancelled the operation'); - } -} +// Moves a layer +function positionLayer(lyr, x, y, alignment){ + if(lyr.iisBackgroundLayer||lyr.positionLocked) return + var layerBounds = lyr.bounds; + var layerX = layerBounds[0].value; + if (alignment == 'top' || alignment == null) + var layerY = layerBounds[1].value; + else if (alignment == 'bottom') + var layerY = layerBounds[3].value; + var deltaX = x-layerX; + var deltaY = y-layerY; + lyr.translate(deltaX, deltaY); +} +// Prints an error message. function err(e) { return 'ERROR: ' + e.message + ' at ' + e.fileName + ':' + e.line; +} + +function bounds(lyr) { + return ('"Bounds": [[' + lyr.bounds[0] + ',' + + lyr.bounds[1] + '],[' + lyr.bounds[2] + ',' + + lyr.bounds[3] + ']]').replace(/ px/g, ""); } \ No newline at end of file diff --git a/scripts/moveLayer.jsx b/scripts/moveLayer.jsx new file mode 100644 index 0000000..c3a4cf9 --- /dev/null +++ b/scripts/moveLayer.jsx @@ -0,0 +1,6 @@ +#include lib.js +var stdout = newFile(arguments[0]); +var lyr = eval(arguments[1]); +lyr.translate((Number)(arguments[2]), (Number)(arguments[3])); +stdout.writeln('{' + bounds(lyr) + '}') +stdout.close(); \ No newline at end of file diff --git a/scripts/setLayerVisibility.jsx b/scripts/setLayerVisibility.jsx deleted file mode 100644 index ce52ae4..0000000 --- a/scripts/setLayerVisibility.jsx +++ /dev/null @@ -1,11 +0,0 @@ -#include lib.js -var stdout = newFile(arguments[0]) -var lyrs = getLayers(arguments[1]) -var vis = arguments[2] == "true" -try { - for (var i = 0; i < lyrs.layers.length; i++) - lyrs.layers[i].visible = vis -} catch (e) { - stdout.writeln(err(e)) -} -stdout.close() \ No newline at end of file diff --git a/scripts/skirmish.jsx b/scripts/skirmish.jsx index 2f4b643..09f88ea 100644 --- a/scripts/skirmish.jsx +++ b/scripts/skirmish.jsx @@ -24,47 +24,34 @@ function main() { formatText() } -DeckCardPSD.prototype.formatText = function() { - var speed = this.textLayers.getByName('speed'); +function formatText() { + + var lyrs = getLayers(arguments[1]) + + var speed = lyrs.getByName('speed'); if (speed.visible) { this.changeStroke(speed, (speed.textItem.contents == 1) ? [128, 128, 128] : [255, 255, 255], this.colors.banner) } - /** - * The lowest we allow a text layer to go. - * @type {int} - */ var bottom = this.doc.height-this.tolerance.flavor_text - // Get our text layers. - var short_text = this.setTextLayer('short_text', undefined, null, 'Arial', 'Regular',[this.bold_words, "Bold"]); - var long_text = this.textLayers.getByName('long_text'); - var flavor_text = this.textLayers.getByName('flavor_text'); + // var short_text = this.setTextLayer('short_text', undefined, null, 'Arial', 'Regular',[this.bold_words, "Bold"]); + var short_text = lyrs.getByName('short_text') + var long_text = lyrs.getByName('long_text'); + var flavor_text = lyrs.getByName('flavor_text'); - // Position the layers. positionLayer(this.short_textBackground, this.short_textBackground.bounds[0], short_text.bounds[3] + this.tolerance.short_text, 'bottom'); positionLayer(long_text, long_text.bounds[0], this.short_textBackground.bounds[3] + this.tolerance.long_text, 'top'); positionLayer(flavor_text, flavor_text.bounds[0], bottom, 'bottom'); - /** - * Make our layers visible - * @todo hack, fix. - */ short_text.visible = short_text.textItem.contents != "“"; long_text.visible = long_text.textItem.contents != "“"; flavor_text.visible = flavor_text.textItem.contents != "“"; - - - //Hide long_text if too long. if (long_text.bounds[3] > this.doc.height - bottom) { long_text.visible == false; } - this.log.debug(short_text.bounds) - this.log.debug(long_text.bounds) - this.log.debug(flavor_text.bounds) - //Hide flavor text if too long. if ( (long_text.visible && flavor_text.bounds[1] < long_text.bounds[3]) || (short_text.visible && flavor_text.bounds[1] < short_text.bounds[3])) { flavor_text.visible = false; diff --git a/structs.go b/structs.go index 141111b..f3d3c9d 100644 --- a/structs.go +++ b/structs.go @@ -1,25 +1,144 @@ package ps -// type layer interface { -// Name() string -// TextItem() []string -// } +import ( + "encoding/json" + "fmt" +) + +type Group interface { + Parent() Group + GetArtLayers() []*ArtLayer + GetLayerSets() []*LayerSet +} + +type Document struct { + Name string + Height int + Width int + ArtLayers []*ArtLayer + LayerSets []*LayerSet +} + +func (d *Document) GetArtLayers() []*ArtLayer { + return d.ArtLayers +} + +func (d *Document) GetLayerSets() []*LayerSet { + return d.LayerSets +} + +func (d *Document) Parent() Group { + return nil +} + +// ActiveDocument returns a Document object from Photoshop's active document. +func ActiveDocument() (Document, error) { + byt, err := DoJs("getActiveDocument.jsx") + var d Document + err = json.Unmarshal(byt, &d) + fmt.Println(string(byt)) + return d, err +} + +func GetDocument() (*Document, error) { + byt, err := DoJs("getActiveDoc.jsx") + var d *Document + err = json.Unmarshal(byt, &d) + for _, lyr := range d.ArtLayers { + lyr.Parent = d + } + for _, set := range d.LayerSets { + set.parent = d + for _, lyr := range set.ArtLayers { + lyr.Parent = set + } + } + fmt.Println(string(byt)) + return d, err +} type ArtLayer struct { - Name string - TextItem string - Bounds [2][2]int - LayerSet string + Name string + // TextItem string + Bounds [2][2]int + Parent Group + Path string + Visiblity bool } +// func (a *ArtLayer) setParent(c Group) { +// ArtLayer.Parent = c +// } + +// Layer returns an ArtLayer from the active document given a specified +// path string. +func Layer(path string) (ArtLayer, error) { + byt, err := DoJs("getLayer.jsx", JSLayer(path)) + var out ArtLayer + err = json.Unmarshal(byt, &out) + if err != nil { + return ArtLayer{}, err + } + out.Path = path + return out, err +} + +// SetVisible makes the layer visible. func (a *ArtLayer) SetVisible() { - DoJs("setLayerVisibility.jsx", a.LayerSet+"/"+a.Name, "true") + js := JSLayer(a.Path) + fmt.Sprintf(".visible=%s;", true) + DoJs("compilejs.jsx", js) } -// func (a *ArtLayer) Name() string { -// return a.name -// } +// Position moves the layer to pos(x, y), measuring from +// the top or bottom left-hand corner. +// TODO: Improve +func (a *ArtLayer) Position(x, y int, align string) { + var lyrX, lyrY int + lyrX = a.Bounds[0][0] + if align != "bottom" { + lyrY = a.Bounds[0][1] + } else { + lyrY = a.Bounds[1][1] + } + byt, err := DoJs("moveLayer.jsx", JSLayer(a.Path), fmt.Sprint(x-lyrX), fmt.Sprint(y-lyrY)) + if err != nil { + panic(err) + } + fmt.Println("byte", string(byt)) + fmt.Println("bounds", a.Bounds) + json.Unmarshal(byt, &a) + fmt.Println("after", a.Bounds) +} -// func (a *ArtLayer) TextItem() string { -// return a.textItem -// } +type LayerSet struct { + Name string + parent Group + ArtLayers []*ArtLayer + LayerSets []*LayerSet +} + +func (l *LayerSet) GetArtLayers() []*ArtLayer { + return l.ArtLayers +} + +func (l *LayerSet) GetLayerSets() []*LayerSet { + return l.LayerSets +} + +func (l *LayerSet) Parent() Group { + return l.parent +} + +func GetLayerSet(path string) (LayerSet, error) { + byt, err := DoJs("getLayerSet.jsx", JSLayer(path)) + var out LayerSet + fmt.Println(string(byt)) // TODO: Debug + err = json.Unmarshal(byt, &out) + if err != nil { + return LayerSet{}, err + } + for _, lyr := range out.ArtLayers { + lyr.Parent = &out + } + return out, err +} From 0202d5c188dbe0a7c4a515c0655eac49348ead27 Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 19 Mar 2018 15:32:46 -0400 Subject: [PATCH 11/22] Document now accessible through go --- ps.go | 2 + ps_test.go | 103 ++++++++++------------ scripts/getActiveDoc.jsx | 6 +- scripts/getActiveDocument.jsx | 50 ----------- scripts/getLayerSet.jsx | 7 ++ structs.go | 161 ++++++++++++++++++++++++++++------ 6 files changed, 186 insertions(+), 143 deletions(-) delete mode 100644 scripts/getActiveDocument.jsx diff --git a/ps.go b/ps.go index 8ad1195..97dd44f 100644 --- a/ps.go +++ b/ps.go @@ -181,7 +181,9 @@ func ApplyDataset(name string) ([]byte, error) { // JSLayer retrurns javascript code to get the layer with a given path. func JSLayer(path string) string { + path = strings.TrimLeft(path, "/") pth := strings.Split(path, "/") + // fmt.Println(path) js := "app.activeDocument" last := len(pth) - 1 if last > 0 { diff --git a/ps_test.go b/ps_test.go index 4c6ce68..55808f3 100644 --- a/ps_test.go +++ b/ps_test.go @@ -2,11 +2,12 @@ package ps import ( "fmt" - "os" - "path/filepath" + // "os" + // "path/filepath" "testing" ) +/* func TestPkgPath(t *testing.T) { out := filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "sbrow", "ps") if filepath.Join(pkgpath) != out { @@ -21,7 +22,6 @@ func TestStart(t *testing.T) { } } -/* func TestOpen(t *testing.T) { if testing.Short() { t.Skip("Skipping \"TestOpen\"") @@ -31,8 +31,7 @@ func TestOpen(t *testing.T) { t.Fatal(err) } } -*/ -/* + func TestClose(t *testing.T) { if testing.Short() { t.Skip("Skipping \"TestClose\"") @@ -107,81 +106,55 @@ func TestSaveAs(t *testing.T) { } } -func TestLayers(t *testing.T) { - l, err := Layers("Areas/TitleBackground/") - fmt.Println(l) +func TestLayerSet(t *testing.T) { + _, err := NewLayerSet("Areas/TitleBackground/") if err != nil { t.Fatal(err) } } -*/ -/* + func TestLayer(t *testing.T) { _, err := Layer("Border/Inner Border") if err != nil { t.Fatal(err) } -}*/ +} + -/*func TestMove(t *testing.T) { +func TestMove(t *testing.T) { lyr, err := Layer("Group 1/Layer 1") if err != nil { t.Fatal(err) } lyr.Position(100, 50, "top") -}*/ - -/* -func TestLayerSet(t *testing.T) { - set, err := GetLayerSet("Indicators/") - fmt.Println(set) - fmt.Println(set.ArtLayers[0].Parent) - if err != nil { - t.Fatal(err) - } } */ - -func TestDocument(t *testing.T) { - d, err := GetDocument() - fmt.Println(d) - fmt.Println(d.ArtLayers[0]) - fmt.Println(d.ArtLayers[0].Parent) - fmt.Println(d.LayerSets[0]) - fmt.Println(d.LayerSets[0].Parent) - fmt.Println(d.LayerSets[0].ArtLayers[0]) - fmt.Println(d.LayerSets[0].ArtLayers[0].Parent) - fmt.Println(d.LayerSets[0].ArtLayers[0].Parent.Parent()) +func TestActiveDocument(t *testing.T) { + if testing.Short() { + t.Skip("Skipping \"TestDocument\"") + } + d, err := ActiveDocument() if err != nil { t.Fatal(err) } - if d != d.ArtLayers[0].Parent { - t.Fatal("Fucked") + if d != d.ArtLayers[0].Parent() { + fmt.Println(d) + fmt.Println(d.ArtLayers[0].Parent()) + t.Fatal("ArtLayers do not have doc as parent.") } if d != d.LayerSets[0].Parent() { - t.Fatal("Fucked") + fmt.Println(d) + fmt.Println(d.LayerSets[0].Parent()) + t.Fatal("LayerSets do not have doc as parent.") } - if d.LayerSets[0] != d.LayerSets[0].ArtLayers[0].Parent { - t.Fatal("Fucked") + if d.LayerSets[0] != d.LayerSets[0].ArtLayers[0].Parent() { + fmt.Println(d.LayerSets[0]) + fmt.Println(d.LayerSets[0].ArtLayers[0]) + fmt.Println(d.LayerSets[0].ArtLayers[0].Parent()) + t.Fatal("Layerset's ArtLayers do not have correct parents") } - } -/*func TestActiveDocument(t *testing.T) { - e, err := DoJs("compilejs.jsx", "alert('testing!')") - fmt.Println(string(e)) - if err != nil { - t.Fatal(err) - } - doc, err := ActiveDocument() - fmt.Println(doc) - if err != nil { - t.Fatal(err) - } -} -*/ - -/* func TestApplyDataset(t *testing.T) { out := []byte("done!\r\n") ret, err := ApplyDataset("Anger") @@ -197,14 +170,26 @@ func TestApplyDataset(t *testing.T) { t.Fatal(err) } } -*/ - -/*func TestDoJs_HideLayer(t *testing.T) { - _, err := DoJs("setLayerVisibility.jsx", "Areas/TitleBackground", "false") +func TestDoJs_HideLayer(t *testing.T) { + err := Open("F:\\GitLab\\dreamkeepers-psd\\Template009.1.psd") if err != nil { t.Fatal(err) } -}*/ + lyr, err := NewLayerSet("Areas/TitleBackground") + lyr.SetVisible(false) + if err != nil { + t.Fatal(err) + } +} + +func BenchmarkDoc_Go(b *testing.B) { + for i := 0; i < b.N; i++ { + _, err := ActiveDocument() + if err != nil { + b.Fatal(err) + } + } +} //.8s //.15 diff --git a/scripts/getActiveDoc.jsx b/scripts/getActiveDoc.jsx index 465d260..b473248 100644 --- a/scripts/getActiveDoc.jsx +++ b/scripts/getActiveDoc.jsx @@ -24,11 +24,7 @@ function lyrSets(sets, nm) { for (var i = 0; i < sets.length; i++) { var set = sets[i]; var name = nm + set.name + "/"; - stdout.write('{"Name": "' + set.name + '", "LayerSets": ['); - // lyrSets(set.layerSets, name); - stdout.write('], "ArtLayers": ['); - layers(set.artLayers); - stdout.write(']}'); + stdout.write('{"Name": "' + set.name + '"}'); if (i+1 != sets.length) stdout.write(','); } diff --git a/scripts/getActiveDocument.jsx b/scripts/getActiveDocument.jsx deleted file mode 100644 index 9e2cb41..0000000 --- a/scripts/getActiveDocument.jsx +++ /dev/null @@ -1,50 +0,0 @@ -#include lib.js - -var stdout = newFile(arguments[0]); -var doc = app.activeDocument; - -stdout.write(('{"Name": "' + doc.name +'", "Height":' +doc.height + - ', "Width":' + doc.width + ", ").replace(/ px/g, "")); - -function layersNsets(obj) { - stdout.write('"ArtLayers": ['); - lyrss(obj.artLayers, "") - stdout.write('], "LayerSets": ['); - lyrSets(obj.layerSets, ""); - // stdout.write('], "LayerSets": ['); -} - -function lyrss(lyrs, set) { - if (typeof lyrs === 'undefined') - return; - for (var i = 0; i < lyrs.length; i++) { - var lyr = lyrs[i]; - stdout.write(('{"Name":"' + lyr.name + '", "Bounds": [[' + lyr.bounds[0] + ',' + - lyr.bounds[1] + '],[' + lyr.bounds[2] + ',' + - lyr.bounds[3] + ']], "Path": "' + set + - '", "Visible": ' + lyr.visible + '}').replace(/ px/g, "")); - if (i+1 != lyrs.length) - stdout.write(','); - } -} -function lyrSets(sets, nm) { - if (typeof sets === 'undefined') - return; - for (var i = 0; i < sets.length; i++) { - var set = sets[i]; - var name = nm + set.name + "/"; - stdout.write('{"Name": "' + set.name + '", "LayerSets": ['); - lyrSets(set.layerSets, name); - stdout.write('], "Layers": ['); - lyrss(set.artLayers, name); - stdout.write(']}'); - if (i+1 != sets.length) - stdout.write(','); - } -} - -layersNsets(doc) -stdout.writeln(']}'); - -alert(doc.layerSets.getByName("Group 2").layerSets.getByName("Group 1").layers.getByName("Layer 1").name) -stdout.close(); \ No newline at end of file diff --git a/scripts/getLayerSet.jsx b/scripts/getLayerSet.jsx index 8e7b361..eaed0b5 100644 --- a/scripts/getLayerSet.jsx +++ b/scripts/getLayerSet.jsx @@ -11,5 +11,12 @@ for (var i = 0; i < set.artLayers.length; i++) { if (i != set.artLayers.length - 1) stdout.writeln(","); } +stdout.write('], "LayerSets": [') +for (var i = 0; i < set.layerSets.length; i++) { + var set = set.layerSets[i]; + stdout.write('{"Name": "'+ set.name +'"}'); + if (i < set.layerSets.length - 1) + stdout.writeln(","); +} stdout.write("]}") stdout.close(); \ No newline at end of file diff --git a/structs.go b/structs.go index f3d3c9d..c6c0e54 100644 --- a/structs.go +++ b/structs.go @@ -3,15 +3,43 @@ package ps import ( "encoding/json" "fmt" + "log" + "strings" ) +// Group represents a Document or LayerSet. type Group interface { + Name() string Parent() Group + SetParent(Group) + Path() string GetArtLayers() []*ArtLayer GetLayerSets() []*LayerSet } +// Document represents a Photoshop document (PSD file). type Document struct { + name string + Height int + Width int + ArtLayers []*ArtLayer + LayerSets []*LayerSet +} + +func (d *Document) UnmarshalJSON(b []byte) error { + tmp := &DocumentJSON{} + if err := json.Unmarshal(b, &tmp); err != nil { + return err + } + d.name = tmp.Name + d.Height = tmp.Height + d.Width = tmp.Width + d.ArtLayers = tmp.ArtLayers + d.LayerSets = tmp.LayerSets + return nil +} + +type DocumentJSON struct { Name string Height int Width int @@ -19,6 +47,9 @@ type Document struct { LayerSets []*LayerSet } +func (d *Document) Name() string { + return d.name +} func (d *Document) GetArtLayers() []*ArtLayer { return d.ArtLayers } @@ -30,45 +61,68 @@ func (d *Document) GetLayerSets() []*LayerSet { func (d *Document) Parent() Group { return nil } +func (d *Document) SetParent(g Group) {} -// ActiveDocument returns a Document object from Photoshop's active document. -func ActiveDocument() (Document, error) { - byt, err := DoJs("getActiveDocument.jsx") - var d Document - err = json.Unmarshal(byt, &d) - fmt.Println(string(byt)) - return d, err +func (d *Document) Path() string { + return "" } -func GetDocument() (*Document, error) { +func ActiveDocument() (*Document, error) { byt, err := DoJs("getActiveDoc.jsx") var d *Document err = json.Unmarshal(byt, &d) for _, lyr := range d.ArtLayers { - lyr.Parent = d + lyr.SetParent(d) } - for _, set := range d.LayerSets { - set.parent = d - for _, lyr := range set.ArtLayers { - lyr.Parent = set + for i, set := range d.LayerSets { + s, err := NewLayerSet(set.Path() + "/") + if err != nil { + log.Fatal(err) } + d.LayerSets[i] = s + s.SetParent(d) } - fmt.Println(string(byt)) return d, err } type ArtLayer struct { - Name string + name string // TextItem string Bounds [2][2]int - Parent Group - Path string + parent Group Visiblity bool } -// func (a *ArtLayer) setParent(c Group) { -// ArtLayer.Parent = c -// } +type ArtLayerJSON struct { + Name string + Bounds [2][2]int + Parent Group + Visible bool +} + +func (a *ArtLayer) UnmarshalJSON(b []byte) error { + tmp := &ArtLayerJSON{} + if err := json.Unmarshal(b, &tmp); err != nil { + return err + } + a.name = tmp.Name + a.Bounds = tmp.Bounds + a.parent = tmp.Parent + a.Visiblity = tmp.Visible + return nil +} + +func (a *ArtLayer) SetParent(c Group) { + a.parent = c +} + +func (a *ArtLayer) Parent() Group { + return a.parent +} + +func (a *ArtLayer) Path() string { + return fmt.Sprintf("%s%s", a.parent.Path(), a.name) +} // Layer returns an ArtLayer from the active document given a specified // path string. @@ -79,13 +133,12 @@ func Layer(path string) (ArtLayer, error) { if err != nil { return ArtLayer{}, err } - out.Path = path return out, err } // SetVisible makes the layer visible. func (a *ArtLayer) SetVisible() { - js := JSLayer(a.Path) + fmt.Sprintf(".visible=%s;", true) + js := JSLayer(a.Path()) + fmt.Sprintf(".visible=%s;", true) DoJs("compilejs.jsx", js) } @@ -100,7 +153,7 @@ func (a *ArtLayer) Position(x, y int, align string) { } else { lyrY = a.Bounds[1][1] } - byt, err := DoJs("moveLayer.jsx", JSLayer(a.Path), fmt.Sprint(x-lyrX), fmt.Sprint(y-lyrY)) + byt, err := DoJs("moveLayer.jsx", JSLayer(a.Path()), fmt.Sprint(x-lyrX), fmt.Sprint(y-lyrY)) if err != nil { panic(err) } @@ -111,12 +164,35 @@ func (a *ArtLayer) Position(x, y int, align string) { } type LayerSet struct { - Name string + name string parent Group ArtLayers []*ArtLayer LayerSets []*LayerSet } +type LayerSetJSON struct { + Name string + Parent Group + ArtLayers []*ArtLayer + LayerSets []*LayerSet +} + +func (l *LayerSet) UnmarshalJSON(b []byte) error { + tmp := &LayerSetJSON{} + if err := json.Unmarshal(b, &tmp); err != nil { + return err + } + l.name = tmp.Name + l.parent = tmp.Parent + l.ArtLayers = tmp.ArtLayers + l.LayerSets = tmp.LayerSets + return nil +} + +func (l *LayerSet) Name() string { + return l.name +} + func (l *LayerSet) GetArtLayers() []*ArtLayer { return l.ArtLayers } @@ -125,20 +201,47 @@ func (l *LayerSet) GetLayerSets() []*LayerSet { return l.LayerSets } +func (l *LayerSet) SetParent(c Group) { + l.parent = c +} + func (l *LayerSet) Parent() Group { return l.parent } -func GetLayerSet(path string) (LayerSet, error) { +func (l *LayerSet) Path() string { + if l.parent == nil { + return l.name + } + return fmt.Sprintf("%s%s/", l.parent.Path(), l.name) +} + +func NewLayerSet(path string) (*LayerSet, error) { byt, err := DoJs("getLayerSet.jsx", JSLayer(path)) - var out LayerSet - fmt.Println(string(byt)) // TODO: Debug + // fmt.Println(string(byt)) + var out *LayerSet err = json.Unmarshal(byt, &out) if err != nil { - return LayerSet{}, err + return &LayerSet{}, err } for _, lyr := range out.ArtLayers { - lyr.Parent = &out + lyr.SetParent(out) + } + for i, set := range out.LayerSets { + s, err := NewLayerSet(fmt.Sprintf("%s%s/", path, set.Name())) + if err != nil { + log.Fatal(err) + } + out.LayerSets[i] = s + s.SetParent(out) } return out, err } + +// SetVisible makes the LayerSet visible. +func (s *LayerSet) SetVisible(b bool) { + fmt.Println(s.Path()) + fmt.Println(JSLayer(s.Path())) + js := JSLayer(strings.TrimRight(s.Path(), ";") + fmt.Sprintf(".visible=%v;", b)) + DoJs("compilejs.jsx", js) +} From 8f0e80863c343846fe31872d3d3c01fc39f39287 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 20 Mar 2018 13:32:43 -0400 Subject: [PATCH 12/22] Can now retrieve ArtLayers/LayerSets from Document/LayerSets by name. - Added helper methods to ArtLayer to get each boundry. - Fixed a bug in getActiveDoc.jsx --- ps_test.go | 59 ++++++++---- scripts/getActiveDoc.jsx | 4 +- scripts/getLayerSet.jsx | 4 +- structs.go | 198 +++++++++++++++++++++++++++------------ 4 files changed, 184 insertions(+), 81 deletions(-) diff --git a/ps_test.go b/ps_test.go index 55808f3..f5c579a 100644 --- a/ps_test.go +++ b/ps_test.go @@ -2,12 +2,11 @@ package ps import ( "fmt" - // "os" - // "path/filepath" + "os" + "path/filepath" "testing" ) -/* func TestPkgPath(t *testing.T) { out := filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "sbrow", "ps") if filepath.Join(pkgpath) != out { @@ -23,9 +22,9 @@ func TestStart(t *testing.T) { } func TestOpen(t *testing.T) { - if testing.Short() { - t.Skip("Skipping \"TestOpen\"") - } + // if testing.Short() { + // t.Skip("Skipping \"TestOpen\"") + // } err := Open("F:\\GitLab\\dreamkeepers-psd\\Template009.1.psd") if err != nil { t.Fatal(err) @@ -82,6 +81,9 @@ func TestWait(t *testing.T) { } func TestDoAction_Crop(t *testing.T) { + if testing.Short() { + t.Skip("Skipping \"TestDoAction_Crop\"") + } err := Open("F:\\GitLab\\dreamkeepers-psd\\Template009.1.psd") if err != nil { t.Fatal(err) @@ -93,6 +95,9 @@ func TestDoAction_Crop(t *testing.T) { } func TestDoAction_Undo(t *testing.T) { + if testing.Short() { + t.Skip("Skipping \"TestDoAction_Undo\"") + } err := DoAction("DK", "Undo") if err != nil { t.Fatal(err) @@ -104,8 +109,10 @@ func TestSaveAs(t *testing.T) { if err != nil { t.Fatal(err) } + os.Remove("F:\\TEMP\\test.png") } +/* func TestLayerSet(t *testing.T) { _, err := NewLayerSet("Areas/TitleBackground/") if err != nil { @@ -120,7 +127,6 @@ func TestLayer(t *testing.T) { } } - func TestMove(t *testing.T) { lyr, err := Layer("Group 1/Layer 1") if err != nil { @@ -137,27 +143,27 @@ func TestActiveDocument(t *testing.T) { if err != nil { t.Fatal(err) } - if d != d.ArtLayers[0].Parent() { + if d != d.artLayers[0].Parent() { fmt.Println(d) - fmt.Println(d.ArtLayers[0].Parent()) + fmt.Println(d.artLayers[0].Parent()) t.Fatal("ArtLayers do not have doc as parent.") } - if d != d.LayerSets[0].Parent() { + if d != d.layerSets[0].Parent() { fmt.Println(d) - fmt.Println(d.LayerSets[0].Parent()) + fmt.Println(d.layerSets[0].Parent()) t.Fatal("LayerSets do not have doc as parent.") } - if d.LayerSets[0] != d.LayerSets[0].ArtLayers[0].Parent() { - fmt.Println(d.LayerSets[0]) - fmt.Println(d.LayerSets[0].ArtLayers[0]) - fmt.Println(d.LayerSets[0].ArtLayers[0].Parent()) + if d.layerSets[0] != d.layerSets[0].artLayers[0].Parent() { + fmt.Println(d.layerSets[0]) + fmt.Println(d.layerSets[0].artLayers[0]) + fmt.Println(d.layerSets[0].artLayers[0].Parent()) t.Fatal("Layerset's ArtLayers do not have correct parents") } } func TestApplyDataset(t *testing.T) { out := []byte("done!\r\n") - ret, err := ApplyDataset("Anger") + ret, err := ApplyDataset(" Anger") if err != nil { t.Fatal(err) } @@ -165,11 +171,28 @@ func TestApplyDataset(t *testing.T) { fail := fmt.Sprintf("TestJS failed.\ngot:\t\"%s\"\nwant:\t\"%s\"", ret, out) t.Fatal(fail) } - err = Quit(2) +} + +func TestDocumentLayerSet(t *testing.T) { + d, err := ActiveDocument() if err != nil { t.Fatal(err) } + set := d.LayerSet("Text") + fmt.Println(set) + for _, lyr := range set.ArtLayers() { + fmt.Println(lyr.name) + } + lyr := set.ArtLayer("id") + fmt.Println(lyr) + set = d.LayerSet("Indicators").LayerSet("Life") + fmt.Println(set) + for _, lyr := range set.ArtLayers() { + fmt.Println(lyr.name) + } } + +/* func TestDoJs_HideLayer(t *testing.T) { err := Open("F:\\GitLab\\dreamkeepers-psd\\Template009.1.psd") if err != nil { @@ -181,7 +204,7 @@ func TestDoJs_HideLayer(t *testing.T) { t.Fatal(err) } } - +*/ func BenchmarkDoc_Go(b *testing.B) { for i := 0; i < b.N; i++ { _, err := ActiveDocument() diff --git a/scripts/getActiveDoc.jsx b/scripts/getActiveDoc.jsx index b473248..eb14b4e 100644 --- a/scripts/getActiveDoc.jsx +++ b/scripts/getActiveDoc.jsx @@ -1,8 +1,8 @@ #include lib.js var stdout = newFile(arguments[0]); var doc = app.activeDocument; -stdout.writeln(('{"Name": "' + doc.name +'", "Height":' +doc.height + - ', "Width":' + doc.width + ', "ArtLayers": [').replace(/ px/g, "")); +stdout.writeln(('{"Name": "'+doc.name+'", "Height":'+doc.height+ + ', "Width":'+doc.width+', "ArtLayers": [').replace(/ px/g, "")); function layers(lyrs) { if (typeof lyrs === 'undefined') return; diff --git a/scripts/getLayerSet.jsx b/scripts/getLayerSet.jsx index eaed0b5..c0eae16 100644 --- a/scripts/getLayerSet.jsx +++ b/scripts/getLayerSet.jsx @@ -13,8 +13,8 @@ for (var i = 0; i < set.artLayers.length; i++) { } stdout.write('], "LayerSets": [') for (var i = 0; i < set.layerSets.length; i++) { - var set = set.layerSets[i]; - stdout.write('{"Name": "'+ set.name +'"}'); + var s = set.layerSets[i]; + stdout.write('{"Name": "'+ s.name +'"}'); if (i < set.layerSets.length - 1) stdout.writeln(","); } diff --git a/structs.go b/structs.go index c6c0e54..94e20e2 100644 --- a/structs.go +++ b/structs.go @@ -2,6 +2,7 @@ package ps import ( "encoding/json" + "flag" "fmt" "log" "strings" @@ -13,13 +14,21 @@ type Group interface { Parent() Group SetParent(Group) Path() string - GetArtLayers() []*ArtLayer - GetLayerSets() []*LayerSet + ArtLayers() []*ArtLayer + LayerSets() []*LayerSet } // Document represents a Photoshop document (PSD file). type Document struct { name string + height int + width int + artLayers []*ArtLayer + layerSets []*LayerSet +} + +type DocumentJSON struct { + Name string Height int Width int ArtLayers []*ArtLayer @@ -32,30 +41,42 @@ func (d *Document) UnmarshalJSON(b []byte) error { return err } d.name = tmp.Name - d.Height = tmp.Height - d.Width = tmp.Width - d.ArtLayers = tmp.ArtLayers - d.LayerSets = tmp.LayerSets + d.height = tmp.Height + d.width = tmp.Width + d.artLayers = tmp.ArtLayers + d.layerSets = tmp.LayerSets return nil } -type DocumentJSON struct { - Name string - Height int - Width int - ArtLayers []*ArtLayer - LayerSets []*LayerSet -} - +// Name returns the document's title. +// This fufills the Group interface. func (d *Document) Name() string { return d.name } -func (d *Document) GetArtLayers() []*ArtLayer { - return d.ArtLayers + +// The height of the document, in pixels. +func (d *Document) Height() int { + return d.height +} + +func (d *Document) ArtLayers() []*ArtLayer { + return d.artLayers } -func (d *Document) GetLayerSets() []*LayerSet { - return d.LayerSets +// LayerSets returns all the document's top level LayerSets. +func (d *Document) LayerSets() []*LayerSet { + return d.layerSets +} + +// LayerSet returns the first top level LayerSet matching +// the given name. +func (d *Document) LayerSet(name string) *LayerSet { + for _, set := range d.layerSets { + if set.name == name { + return set + } + } + return nil } func (d *Document) Parent() Group { @@ -68,29 +89,31 @@ func (d *Document) Path() string { } func ActiveDocument() (*Document, error) { + log.Println("Loading ActiveDoucment/") byt, err := DoJs("getActiveDoc.jsx") var d *Document err = json.Unmarshal(byt, &d) - for _, lyr := range d.ArtLayers { + for _, lyr := range d.artLayers { lyr.SetParent(d) } - for i, set := range d.LayerSets { - s, err := NewLayerSet(set.Path() + "/") + for i, set := range d.layerSets { + s, err := NewLayerSet(set.Path()+"/", d) if err != nil { log.Fatal(err) } - d.LayerSets[i] = s - s.SetParent(d) + d.layerSets[i] = s + // s.SetParent(d) } return d, err } +// ArtLayer represents an Art Layer in a photoshop document. type ArtLayer struct { name string // TextItem string - Bounds [2][2]int - parent Group - Visiblity bool + bounds [2][2]int + parent Group + visible bool } type ArtLayerJSON struct { @@ -106,12 +129,40 @@ func (a *ArtLayer) UnmarshalJSON(b []byte) error { return err } a.name = tmp.Name - a.Bounds = tmp.Bounds + a.bounds = tmp.Bounds a.parent = tmp.Parent - a.Visiblity = tmp.Visible + a.visible = tmp.Visible return nil } +func (a *ArtLayer) Name() string { + return a.name +} + +func (a *ArtLayer) Bounds() [2][2]int { + return a.bounds +} + +// X1 returns the layer's leftmost x value. +func (a *ArtLayer) X1() int { + return a.bounds[0][0] +} + +// X2 returns the layer's rightmost x value. +func (a *ArtLayer) X2() int { + return a.bounds[1][0] +} + +// Y1 returns the layer's topmost Y value. +func (a *ArtLayer) Y1() int { + return a.bounds[0][1] +} + +// Y2 returns the layer's bottommost y value. +func (a *ArtLayer) Y2() int { + return a.bounds[1][1] +} + func (a *ArtLayer) SetParent(c Group) { a.parent = c } @@ -137,37 +188,41 @@ func Layer(path string) (ArtLayer, error) { } // SetVisible makes the layer visible. -func (a *ArtLayer) SetVisible() { - js := JSLayer(a.Path()) + fmt.Sprintf(".visible=%s;", true) +func (a *ArtLayer) SetVisible(b bool) { + js := fmt.Sprintf("%s.visible=%v;", + strings.TrimRight(JSLayer(a.Path()), ";"), b) + log.Printf("Setting %s.Visible to %v\n", a.name, b) DoJs("compilejs.jsx", js) } -// Position moves the layer to pos(x, y), measuring from -// the top or bottom left-hand corner. +// Visible returns whether or not the layer is currently hidden. +func (a *ArtLayer) Visible() bool { + return a.visible +} + +// SetPos snaps the given layer boundry to the given point. +// Valid options for bound are: TL, TR, BL, BR // TODO: Improve -func (a *ArtLayer) Position(x, y int, align string) { +func (a *ArtLayer) SetPos(x, y int, bound string) { var lyrX, lyrY int - lyrX = a.Bounds[0][0] - if align != "bottom" { - lyrY = a.Bounds[0][1] - } else { - lyrY = a.Bounds[1][1] + lyrX = a.X1() + if bound != "TL" { + lyrY = a.Y2() + } else { // "BL" + lyrY = a.Y1() } byt, err := DoJs("moveLayer.jsx", JSLayer(a.Path()), fmt.Sprint(x-lyrX), fmt.Sprint(y-lyrY)) if err != nil { panic(err) } - fmt.Println("byte", string(byt)) - fmt.Println("bounds", a.Bounds) json.Unmarshal(byt, &a) - fmt.Println("after", a.Bounds) } type LayerSet struct { name string parent Group - ArtLayers []*ArtLayer - LayerSets []*LayerSet + artLayers []*ArtLayer + layerSets []*LayerSet } type LayerSetJSON struct { @@ -184,8 +239,8 @@ func (l *LayerSet) UnmarshalJSON(b []byte) error { } l.name = tmp.Name l.parent = tmp.Parent - l.ArtLayers = tmp.ArtLayers - l.LayerSets = tmp.LayerSets + l.artLayers = tmp.ArtLayers + l.layerSets = tmp.LayerSets return nil } @@ -193,12 +248,34 @@ func (l *LayerSet) Name() string { return l.name } -func (l *LayerSet) GetArtLayers() []*ArtLayer { - return l.ArtLayers +func (l *LayerSet) ArtLayers() []*ArtLayer { + return l.artLayers +} + +// ArtLayer returns the first top level ArtLayer matching +// the given name. +func (l *LayerSet) ArtLayer(name string) *ArtLayer { + for _, lyr := range l.artLayers { + if lyr.name == name { + return lyr + } + } + return nil +} + +func (l *LayerSet) LayerSets() []*LayerSet { + return l.layerSets } -func (l *LayerSet) GetLayerSets() []*LayerSet { - return l.LayerSets +// LayerSet returns the first top level LayerSet matching +// the given name. +func (l *LayerSet) LayerSet(name string) *LayerSet { + for _, set := range l.layerSets { + if set.name == name { + return set + } + } + return nil } func (l *LayerSet) SetParent(c Group) { @@ -216,32 +293,35 @@ func (l *LayerSet) Path() string { return fmt.Sprintf("%s%s/", l.parent.Path(), l.name) } -func NewLayerSet(path string) (*LayerSet, error) { +func NewLayerSet(path string, g Group) (*LayerSet, error) { byt, err := DoJs("getLayerSet.jsx", JSLayer(path)) - // fmt.Println(string(byt)) var out *LayerSet err = json.Unmarshal(byt, &out) + if flag.Lookup("test.v") != nil { + // log.Println(string(byt)) + } + out.SetParent(g) + log.Printf("Loading ActiveDocument/%s\n", out.Path()) if err != nil { return &LayerSet{}, err } - for _, lyr := range out.ArtLayers { + for _, lyr := range out.artLayers { lyr.SetParent(out) } - for i, set := range out.LayerSets { - s, err := NewLayerSet(fmt.Sprintf("%s%s/", path, set.Name())) + for i, set := range out.layerSets { + // log.Println("\t", set.name) + s, err := NewLayerSet(fmt.Sprintf("%s%s/", path, set.Name()), out) if err != nil { log.Fatal(err) } - out.LayerSets[i] = s + out.layerSets[i] = s s.SetParent(out) } return out, err } // SetVisible makes the LayerSet visible. -func (s *LayerSet) SetVisible(b bool) { - fmt.Println(s.Path()) - fmt.Println(JSLayer(s.Path())) - js := JSLayer(strings.TrimRight(s.Path(), ";") + fmt.Sprintf(".visible=%v;", b)) +func (l *LayerSet) SetVisible(b bool) { + js := fmt.Sprintf("%s%v", JSLayer(strings.TrimRight(l.Path(), ";")), b) DoJs("compilejs.jsx", js) } From 71219e64d9564454b047803b3a8e29bd5ec830b1 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 20 Mar 2018 19:15:23 -0400 Subject: [PATCH 13/22] Can now color layers and apply strokes --- ps.go | 1 + ps_test.go | 26 ++++++++- scripts/colorLayer.vbs | 76 +++++++++++++++++++++++++ scripts/colorStroke.vbs | 120 ++++++++++++++++++++++++++++++++++++++++ scripts/lib.js | 14 ----- scripts/skirmish.jsx | 59 -------------------- structs.go | 109 ++++++++++++++++++++++++++++++++++++ 7 files changed, 331 insertions(+), 74 deletions(-) create mode 100644 scripts/colorLayer.vbs create mode 100644 scripts/colorStroke.vbs delete mode 100644 scripts/skirmish.jsx diff --git a/ps.go b/ps.go index 97dd44f..dc30302 100644 --- a/ps.go +++ b/ps.go @@ -11,6 +11,7 @@ import ( "errors" "fmt" "io/ioutil" + // "log" "os" "os/exec" "path/filepath" diff --git a/ps_test.go b/ps_test.go index f5c579a..53910a1 100644 --- a/ps_test.go +++ b/ps_test.go @@ -159,8 +159,29 @@ func TestActiveDocument(t *testing.T) { fmt.Println(d.layerSets[0].artLayers[0].Parent()) t.Fatal("Layerset's ArtLayers do not have correct parents") } + // d.LayerSet("Areas").LayerSet("Bottom").ArtLayer("L Bar").SetColor(155, 255, 255) + lyr := d.LayerSet("Text").ArtLayer("speed") + if lyr == nil { + t.Fatal("lyr does not exist") + } + s := Stroke{Size: 4, Color: &RGB{0, 0, 0}} + lyr.SetStroke(s, &RGB{128, 128, 128}) + +} + +/* +func TestColor(t *testing.T) { + byt, err := run("colorLayer.vbs", "255", "255", "255") + fmt.Println(string(byt)) + fmt.Println(err) + if err != nil { + + t.Fatal() + } } +*/ +/* func TestApplyDataset(t *testing.T) { out := []byte("done!\r\n") ret, err := ApplyDataset(" Anger") @@ -174,6 +195,9 @@ func TestApplyDataset(t *testing.T) { } func TestDocumentLayerSet(t *testing.T) { + if testing.Short() { + t.Skip("Skipping TestDocumentLayerSet") + } d, err := ActiveDocument() if err != nil { t.Fatal(err) @@ -191,7 +215,7 @@ func TestDocumentLayerSet(t *testing.T) { fmt.Println(lyr.name) } } - +*/ /* func TestDoJs_HideLayer(t *testing.T) { err := Open("F:\\GitLab\\dreamkeepers-psd\\Template009.1.psd") diff --git a/scripts/colorLayer.vbs b/scripts/colorLayer.vbs new file mode 100644 index 0000000..cd5dc29 --- /dev/null +++ b/scripts/colorLayer.vbs @@ -0,0 +1,76 @@ +DIM objApp +SET objApp = CreateObject("Photoshop.Application") +DIM dialogMode +dialogMode = 3 +DIM idsetd +idsetd = objApp.CharIDToTypeID("setd") + DIM desc134 + SET desc134 = CreateObject("Photoshop.ActionDescriptor") + DIM idnull + idnull = objApp.CharIDToTypeID("null") + DIM ref44 + SET ref44 = CreateObject("Photoshop.ActionReference") + DIM idPrpr + idPrpr = objApp.CharIDToTypeID("Prpr") + DIM idLefx + idLefx = objApp.CharIDToTypeID("Lefx") + Call ref44.PutProperty(idPrpr, idLefx) + DIM idLyr + idLyr = objApp.CharIDToTypeID("Lyr ") + DIM idOrdn + idOrdn = objApp.CharIDToTypeID("Ordn") + DIM idTrgt + idTrgt = objApp.CharIDToTypeID("Trgt") + Call ref44.PutEnumerated(idLyr, idOrdn, idTrgt) + Call desc134.PutReference(idnull, ref44) + DIM idT + idT = objApp.CharIDToTypeID("T ") + DIM desc135 + SET desc135 = CreateObject("Photoshop.ActionDescriptor") + DIM idScl + idScl = objApp.CharIDToTypeID("Scl ") + DIM idPrc + idPrc = objApp.CharIDToTypeID("#Prc") + Call desc135.PutUnitDouble(idScl, idPrc, 416.666667) + DIM idSoFi + idSoFi = objApp.CharIDToTypeID("SoFi") + DIM desc136 + SET desc136 = CreateObject("Photoshop.ActionDescriptor") + DIM idenab + idenab = objApp.CharIDToTypeID("enab") + Call desc136.PutBoolean(idenab, True) + DIM idMd + idMd = objApp.CharIDToTypeID("Md ") + DIM idBlnM + idBlnM = objApp.CharIDToTypeID("BlnM") + DIM idNrml + idNrml = objApp.CharIDToTypeID("Nrml") + Call desc136.PutEnumerated(idMd, idBlnM, idNrml) + DIM idOpct + idOpct = objApp.CharIDToTypeID("Opct") + idPrc = objApp.CharIDToTypeID("#Prc") + Call desc136.PutUnitDouble(idOpct, idPrc, 100.000000) + DIM idClr + idClr = objApp.CharIDToTypeID("Clr ") + DIM desc137 + SET desc137 = CreateObject("Photoshop.ActionDescriptor") + DIM idRd + idRd = objApp.CharIDToTypeID("Rd ") + Call desc137.PutDouble(idRd, CInt(wScript.Arguments(0))) + ' Call desc137.PutDouble(idRd, 255) + DIM idGrn + idGrn = objApp.CharIDToTypeID("Grn ") + Call desc137.PutDouble(idGrn, Cint(wScript.Arguments(1))) + ' Call desc137.PutDouble(idGrn, 255) + DIM idBl + idBl = objApp.CharIDToTypeID("Bl ") + Call desc137.PutDouble(idBl, CInt(wScript.Arguments(2))) + ' Call desc137.PutDouble(idBl, 255) + DIM idRGBC + idRGBC = objApp.CharIDToTypeID("RGBC") + Call desc136.PutObject(idClr, idRGBC, desc137) + idSoFi = objApp.CharIDToTypeID("SoFi") + Call desc135.PutObject(idSoFi, idSoFi, desc136) + idLefx = objApp.CharIDToTypeID("Lefx") + Call desc134.PutObject(idT, idLefx, desc135) +Call objApp.ExecuteAction(idsetd, desc134, dialogMode) \ No newline at end of file diff --git a/scripts/colorStroke.vbs b/scripts/colorStroke.vbs new file mode 100644 index 0000000..b2b1f92 --- /dev/null +++ b/scripts/colorStroke.vbs @@ -0,0 +1,120 @@ +DIM objApp +SET objApp = CreateObject("Photoshop.Application") +REM Use dialog mode 3 for show no dialogs +DIM dialogMode +dialogMode = 3 +DIM idsetd +idsetd = objApp.CharIDToTypeID("setd") + DIM desc2 + SET desc2 = CreateObject("Photoshop.ActionDescriptor") + DIM idnull + idnull = objApp.CharIDToTypeID("null") + DIM ref2 + SET ref2 = CreateObject("Photoshop.ActionReference") + DIM idPrpr + idPrpr = objApp.CharIDToTypeID("Prpr") + DIM idLefx + idLefx = objApp.CharIDToTypeID("Lefx") + Call ref2.PutProperty(idPrpr, idLefx) + DIM idLyr + idLyr = objApp.CharIDToTypeID("Lyr ") + DIM idOrdn + idOrdn = objApp.CharIDToTypeID("Ordn") + DIM idTrgt + idTrgt = objApp.CharIDToTypeID("Trgt") + Call ref2.PutEnumerated(idLyr, idOrdn, idTrgt) + Call desc2.PutReference(idnull, ref2) + DIM idT + idT = objApp.CharIDToTypeID("T ") + DIM desc3 + SET desc3 = CreateObject("Photoshop.ActionDescriptor") + DIM idScl + idScl = objApp.CharIDToTypeID("Scl ") + DIM idPrc + idPrc = objApp.CharIDToTypeID("#Prc") + Call desc3.PutUnitDouble(idScl, idPrc, 416.666667) + DIM idSoFi + idSoFi = objApp.CharIDToTypeID("SoFi") + DIM desc4 + SET desc4 = CreateObject("Photoshop.ActionDescriptor") + DIM idenab + idenab = objApp.CharIDToTypeID("enab") + Call desc4.PutBoolean(idenab, True) + DIM idMd + idMd = objApp.CharIDToTypeID("Md ") + DIM idBlnM + idBlnM = objApp.CharIDToTypeID("BlnM") + DIM idNrml + idNrml = objApp.CharIDToTypeID("Nrml") + Call desc4.PutEnumerated(idMd, idBlnM, idNrml) + DIM idOpct + idOpct = objApp.CharIDToTypeID("Opct") + idPrc = objApp.CharIDToTypeID("#Prc") + Call desc4.PutUnitDouble(idOpct, idPrc, 100.000000) + DIM idClr + idClr = objApp.CharIDToTypeID("Clr ") + DIM desc5 + SET desc5 = CreateObject("Photoshop.ActionDescriptor") + DIM idRd + idRd = objApp.CharIDToTypeID("Rd ") + Call desc5.PutDouble(idRd, CInt(wScript.Arguments(0))) + DIM idGrn + idGrn = objApp.CharIDToTypeID("Grn ") + Call desc5.PutDouble(idGrn,CInt(wScript.Arguments(1))) + DIM idBl + idBl = objApp.CharIDToTypeID("Bl ") + Call desc5.PutDouble(idBl, CInt(wScript.Arguments(2))) + DIM idRGBC + idRGBC = objApp.CharIDToTypeID("RGBC") + Call desc4.PutObject(idClr, idRGBC, desc5) + idSoFi = objApp.CharIDToTypeID("SoFi") + Call desc3.PutObject(idSoFi, idSoFi, desc4) + DIM idFrFX + idFrFX = objApp.CharIDToTypeID("FrFX") + DIM desc6 + SET desc6 = CreateObject("Photoshop.ActionDescriptor") + idenab = objApp.CharIDToTypeID("enab") + Call desc6.PutBoolean(idenab, True) + DIM idStyl + idStyl = objApp.CharIDToTypeID("Styl") + DIM idFStl + idFStl = objApp.CharIDToTypeID("FStl") + DIM idOutF + idOutF = objApp.CharIDToTypeID("OutF") + Call desc6.PutEnumerated(idStyl, idFStl, idOutF) + DIM idPntT + idPntT = objApp.CharIDToTypeID("PntT") + DIM idFrFl + idFrFl = objApp.CharIDToTypeID("FrFl") + DIM idSClr + idSClr = objApp.CharIDToTypeID("SClr") + Call desc6.PutEnumerated(idPntT, idFrFl, idSClr) + idMd = objApp.CharIDToTypeID("Md ") + idBlnM = objApp.CharIDToTypeID("BlnM") + idNrml = objApp.CharIDToTypeID("Nrml") + Call desc6.PutEnumerated(idMd, idBlnM, idNrml) + idOpct = objApp.CharIDToTypeID("Opct") + idPrc = objApp.CharIDToTypeID("#Prc") + Call desc6.PutUnitDouble(idOpct, idPrc, 100.000000) + DIM idSz + idSz = objApp.CharIDToTypeID("Sz ") + DIM idPxl + idPxl = objApp.CharIDToTypeID("#Pxl") + Call desc6.PutUnitDouble(idSz, idPxl, CLng(wScript.Arguments(3))) + idClr = objApp.CharIDToTypeID("Clr ") + DIM desc7 + SET desc7 = CreateObject("Photoshop.ActionDescriptor") + idRd = objApp.CharIDToTypeID("Rd ") + Call desc7.PutDouble(idRd, CInt(wScript.Arguments(4))) + idGrn = objApp.CharIDToTypeID("Grn ") + Call desc7.PutDouble(idGrn, CInt(wScript.Arguments(5))) + idBl = objApp.CharIDToTypeID("Bl ") + Call desc7.PutDouble(idBl, CInt(wScript.Arguments(6))) + idRGBC = objApp.CharIDToTypeID("RGBC") + Call desc6.PutObject(idClr, idRGBC, desc7) + idFrFX = objApp.CharIDToTypeID("FrFX") + Call desc3.PutObject(idFrFX, idFrFX, desc6) + idLefx = objApp.CharIDToTypeID("Lefx") + Call desc2.PutObject(idT, idLefx, desc3) +Call objApp.ExecuteAction(idsetd, desc2, dialogMode) + diff --git a/scripts/lib.js b/scripts/lib.js index 7046b59..70a5983 100644 --- a/scripts/lib.js +++ b/scripts/lib.js @@ -8,20 +8,6 @@ function newFile(path) { return f } -// Moves a layer -function positionLayer(lyr, x, y, alignment){ - if(lyr.iisBackgroundLayer||lyr.positionLocked) return - var layerBounds = lyr.bounds; - var layerX = layerBounds[0].value; - if (alignment == 'top' || alignment == null) - var layerY = layerBounds[1].value; - else if (alignment == 'bottom') - var layerY = layerBounds[3].value; - var deltaX = x-layerX; - var deltaY = y-layerY; - lyr.translate(deltaX, deltaY); -} - // Prints an error message. function err(e) { return 'ERROR: ' + e.message + ' at ' + e.fileName + ':' + e.line; diff --git a/scripts/skirmish.jsx b/scripts/skirmish.jsx deleted file mode 100644 index 09f88ea..0000000 --- a/scripts/skirmish.jsx +++ /dev/null @@ -1,59 +0,0 @@ -function setTitle(title) { - var nameLayer = this.textLayers.getByName('name'); - var found = false; - for (var i = 0; i < this.titleBackgrounds.length; i++) { - if (!found && (nameLayer.bounds[2] + this.tolerance.title) < this.titleBackgrounds[i].bounds[2]) { - this.log.log('"{0}" is long enough'.format(this.titleBackgrounds[i].name), '-'); - this.titleBackgrounds[i].visible = true; - found = true; - } else { - this.log.log('"{0}" is too short'.format(this.titleBackgrounds[i].name),'-') - this.titleBackgrounds[i].visible = false; - } - } - -} - -function main() { - setTitle() - if ((this.Type).indexOf("Channel") != -2) { - this.changeColor(this.resolveBanner.normal, this.colors.Rarity); - } else { - this.changeColor(this.resolveBanner.normal, [128, 128, 128]); - } - formatText() -} - -function formatText() { - - var lyrs = getLayers(arguments[1]) - - var speed = lyrs.getByName('speed'); - if (speed.visible) { - this.changeStroke(speed, (speed.textItem.contents == 1) ? [128, 128, 128] : [255, 255, 255], - this.colors.banner) - } - var bottom = this.doc.height-this.tolerance.flavor_text - - // var short_text = this.setTextLayer('short_text', undefined, null, 'Arial', 'Regular',[this.bold_words, "Bold"]); - var short_text = lyrs.getByName('short_text') - var long_text = lyrs.getByName('long_text'); - var flavor_text = lyrs.getByName('flavor_text'); - - positionLayer(this.short_textBackground, this.short_textBackground.bounds[0], short_text.bounds[3] + this.tolerance.short_text, 'bottom'); - positionLayer(long_text, long_text.bounds[0], this.short_textBackground.bounds[3] + this.tolerance.long_text, 'top'); - positionLayer(flavor_text, flavor_text.bounds[0], bottom, 'bottom'); - - short_text.visible = short_text.textItem.contents != "“"; - long_text.visible = long_text.textItem.contents != "“"; - flavor_text.visible = flavor_text.textItem.contents != "“"; - - if (long_text.bounds[3] > this.doc.height - bottom) { - long_text.visible == false; - } - - if ( (long_text.visible && flavor_text.bounds[1] < long_text.bounds[3]) - || (short_text.visible && flavor_text.bounds[1] < short_text.bounds[3])) { - flavor_text.visible = false; - } -}; \ No newline at end of file diff --git a/structs.go b/structs.go index 94e20e2..ecfa770 100644 --- a/structs.go +++ b/structs.go @@ -1,6 +1,7 @@ package ps import ( + "encoding/hex" "encoding/json" "flag" "fmt" @@ -8,6 +9,53 @@ import ( "strings" ) +// Color represents a color. +type Color interface { + RGB() [3]int +} + +func Compare(a, b Color) Color { + A := a.RGB() + B := b.RGB() + Aavg := (A[0] + A[1] + A[2]) / 3 + Bavg := (B[0] + B[1] + B[2]) / 3 + if Aavg > Bavg { + return a + } + return b +} + +// Color is a color in RGB format. +type RGB struct { + Red int + Green int + Blue int +} + +// RGB returns the color in RGB format. +func (r *RGB) RGB() [3]int { + return [3]int{r.Red, r.Green, r.Blue} +} + +// Hex is a color in hexidecimal format. +type Hex []uint8 + +func (h Hex) RGB() [3]int { + src := []byte(h) + dst := make([]byte, hex.DecodedLen(len(src))) + _, err := hex.Decode(dst, src) + if err != nil { + panic(err) + } + return [3]int{int(dst[0]), int(dst[1]), int(dst[2])} +} + +// Stroke represents a layer stroke effect. +type Stroke struct { + Size float32 + Color +} + // Group represents a Document or LayerSet. type Group interface { Name() string @@ -114,6 +162,8 @@ type ArtLayer struct { bounds [2][2]int parent Group visible bool + Color + *Stroke } type ArtLayerJSON struct { @@ -167,6 +217,61 @@ func (a *ArtLayer) SetParent(c Group) { a.parent = c } +// SetActive makes this layer active in photoshop. +// Layers need to be active to perform certain operations +func (a *ArtLayer) SetActive() ([]byte, error) { + js := fmt.Sprintf("app.activeDocument.activeLayer=%s", JSLayer(a.Path())) + return DoJs("compilejs.jsx", js) +} + +// SetColor creates a color overlay for the layer +func (a *ArtLayer) SetColor(c Color) { + a.Color = c + cols := c.RGB() + r := cols[0] + g := cols[1] + b := cols[2] + if a.Stroke != nil { + a.SetStroke(Stroke{a.Stroke.Size, a.Stroke.Color}, a.Color) + return + } + byt, err := a.SetActive() + if len(byt) != 0 { + log.Println(string(byt), "err") + } + if err != nil { + log.Panic(err) + } + byt, err = run("colorLayer", fmt.Sprint(r), fmt.Sprint(g), fmt.Sprint(b)) + if len(byt) != 0 { + log.Println(string(byt), "err") + } + if err != nil { + log.Panic(err) + } +} + +func (a *ArtLayer) SetStroke(stk Stroke, fill Color) { + byt, err := a.SetActive() + if len(byt) != 0 { + log.Println(string(byt)) + } + if err != nil { + log.Panic(err) + } + stkCol := stk.Color.RGB() + col := fill.RGB() + byt, err = run("colorStroke", fmt.Sprint(col[0]), fmt.Sprint(col[1]), fmt.Sprint(col[2]), + fmt.Sprintf("%.2f", stk.Size), fmt.Sprint(stkCol[0]), fmt.Sprint(stkCol[1]), fmt.Sprint(stkCol[2])) + if len(byt) != 0 { + log.Println(string(byt)) + } + if err != nil { + log.Panic(err) + } + +} + func (a *ArtLayer) Parent() Group { return a.parent } @@ -204,6 +309,10 @@ func (a *ArtLayer) Visible() bool { // Valid options for bound are: TL, TR, BL, BR // TODO: Improve func (a *ArtLayer) SetPos(x, y int, bound string) { + if !a.visible { + return + } + var lyrX, lyrY int lyrX = a.X1() if bound != "TL" { From daac5bf3d2cd494fb6968a0893de6d0afadd5fcc Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 21 Mar 2018 22:13:17 -0400 Subject: [PATCH 14/22] Added white and gray to colors list. --- structs.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/structs.go b/structs.go index ecfa770..ab960a7 100644 --- a/structs.go +++ b/structs.go @@ -9,6 +9,11 @@ import ( "strings" ) +var Colors map[string]Color = map[string]Color{ + "Gray": &RGB{128, 128, 128}, + "White": &RGB{255, 255, 255}, +} + // Color represents a color. type Color interface { RGB() [3]int From 5b36b9193a09632badc2c8ce990b410480c756aa Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 22 Mar 2018 20:11:51 -0400 Subject: [PATCH 15/22] Can now save and load from json Much improved speed over loading everything manually. --- .gitignore | 2 + colors.go | 57 +++++++++ ps.go | 1 - ps_test.go | 41 +++++-- scripts/activeDocName.jsx | 2 + scripts/getLayer.jsx | 4 +- scripts/isVisible.jsx | 2 + structs.go | 245 ++++++++++++++++++++++++++------------ 8 files changed, 264 insertions(+), 90 deletions(-) create mode 100644 .gitignore create mode 100644 colors.go create mode 100644 scripts/activeDocName.jsx create mode 100644 scripts/isVisible.jsx diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6152548 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ + +data/ diff --git a/colors.go b/colors.go new file mode 100644 index 0000000..c371a05 --- /dev/null +++ b/colors.go @@ -0,0 +1,57 @@ +package ps + +import ( + "encoding/hex" +) + +var Colors map[string]Color = map[string]Color{ + "Gray": &RGB{128, 128, 128}, + "White": &RGB{255, 255, 255}, +} + +// Color represents a color. +type Color interface { + RGB() [3]int +} + +func Compare(a, b Color) Color { + A := a.RGB() + B := b.RGB() + Aavg := (A[0] + A[1] + A[2]) / 3 + Bavg := (B[0] + B[1] + B[2]) / 3 + if Aavg > Bavg { + return a + } + return b +} + +// Color is a color in RGB format. +type RGB struct { + Red int + Green int + Blue int +} + +// RGB returns the color in RGB format. +func (r RGB) RGB() [3]int { + return [3]int{r.Red, r.Green, r.Blue} +} + +// Hex is a color in hexidecimal format. +type Hex []uint8 + +func (h Hex) RGB() [3]int { + src := []byte(h) + dst := make([]byte, hex.DecodedLen(len(src))) + _, err := hex.Decode(dst, src) + if err != nil { + panic(err) + } + return [3]int{int(dst[0]), int(dst[1]), int(dst[2])} +} + +// Stroke represents a layer stroke effect. +type Stroke struct { + Size float32 + Color +} diff --git a/ps.go b/ps.go index dc30302..406b65b 100644 --- a/ps.go +++ b/ps.go @@ -184,7 +184,6 @@ func ApplyDataset(name string) ([]byte, error) { func JSLayer(path string) string { path = strings.TrimLeft(path, "/") pth := strings.Split(path, "/") - // fmt.Println(path) js := "app.activeDocument" last := len(pth) - 1 if last > 0 { diff --git a/ps_test.go b/ps_test.go index 53910a1..34e293b 100644 --- a/ps_test.go +++ b/ps_test.go @@ -1,7 +1,9 @@ package ps import ( + "encoding/json" "fmt" + "io/ioutil" "os" "path/filepath" "testing" @@ -112,9 +114,8 @@ func TestSaveAs(t *testing.T) { os.Remove("F:\\TEMP\\test.png") } -/* func TestLayerSet(t *testing.T) { - _, err := NewLayerSet("Areas/TitleBackground/") + _, err := NewLayerSet("Areas/TitleBackground/", nil) if err != nil { t.Fatal(err) } @@ -132,9 +133,9 @@ func TestMove(t *testing.T) { if err != nil { t.Fatal(err) } - lyr.Position(100, 50, "top") + lyr.SetPos(100, 50, "TL") } -*/ + func TestActiveDocument(t *testing.T) { if testing.Short() { t.Skip("Skipping \"TestDocument\"") @@ -166,10 +167,8 @@ func TestActiveDocument(t *testing.T) { } s := Stroke{Size: 4, Color: &RGB{0, 0, 0}} lyr.SetStroke(s, &RGB{128, 128, 128}) - } -/* func TestColor(t *testing.T) { byt, err := run("colorLayer.vbs", "255", "255", "255") fmt.Println(string(byt)) @@ -179,9 +178,7 @@ func TestColor(t *testing.T) { t.Fatal() } } -*/ -/* func TestApplyDataset(t *testing.T) { out := []byte("done!\r\n") ret, err := ApplyDataset(" Anger") @@ -215,20 +212,40 @@ func TestDocumentLayerSet(t *testing.T) { fmt.Println(lyr.name) } } -*/ -/* + +func TestLoadedDoc(t *testing.T) { + var d *Document + byt, err := ioutil.ReadFile("Document.txt") + if err != nil { + t.Fatal(err) + } + err = json.Unmarshal(byt, &d) + if err != nil { + t.Fatal(err) + } + if d != d.ArtLayers()[0].Parent() { + t.Fatal("Loaded document's ArtLayers do not point to doc") + } + if d != d.LayerSets()[0].Parent() { + t.Fatal("Loaded document's LayerSets do not point to doc") + } + if d.LayerSets()[0] != d.layerSets[0].artLayers[0].Parent() { + t.Fatal("Loaded document's LayerSet's ArtLayers do not point to layerSets") + } +} + func TestDoJs_HideLayer(t *testing.T) { err := Open("F:\\GitLab\\dreamkeepers-psd\\Template009.1.psd") if err != nil { t.Fatal(err) } - lyr, err := NewLayerSet("Areas/TitleBackground") + lyr, err := NewLayerSet("Areas/TitleBackground", nil) lyr.SetVisible(false) if err != nil { t.Fatal(err) } } -*/ + func BenchmarkDoc_Go(b *testing.B) { for i := 0; i < b.N; i++ { _, err := ActiveDocument() diff --git a/scripts/activeDocName.jsx b/scripts/activeDocName.jsx new file mode 100644 index 0000000..ef8543b --- /dev/null +++ b/scripts/activeDocName.jsx @@ -0,0 +1,2 @@ +#include lib.js +var stdout = newFile(arguments[0]);stdout.writeln(app.activeDocument.name);stdout.close(); \ No newline at end of file diff --git a/scripts/getLayer.jsx b/scripts/getLayer.jsx index c42144a..ac439ea 100644 --- a/scripts/getLayer.jsx +++ b/scripts/getLayer.jsx @@ -2,7 +2,7 @@ var stdout = newFile(arguments[0]); var lyr = eval(arguments[1]); -stdout.writeln(('{"Name":"' + lyr.name + '", "Bounds": [[' + lyr.bounds[0] + ',' + +stdout.writeln(('{"Name":"' + lyr.name + '","Bounds":[[' + lyr.bounds[0] + ',' + lyr.bounds[1] + '],[' + lyr.bounds[2] + ',' + - lyr.bounds[3] + ']], "Visible": ' + lyr.visible + '}').replace(/ px/g, "")); + lyr.bounds[3] + ']],"Visible":' + lyr.visible + '}').replace(/ px/g, "")); stdout.close(); \ No newline at end of file diff --git a/scripts/isVisible.jsx b/scripts/isVisible.jsx new file mode 100644 index 0000000..0e926a8 --- /dev/null +++ b/scripts/isVisible.jsx @@ -0,0 +1,2 @@ +#include lib.js +var stdout = newFile(arguments[0]);stdout.writeln(eval(arguments[1]).visible);stdout.close(); \ No newline at end of file diff --git a/structs.go b/structs.go index ab960a7..3d4eead 100644 --- a/structs.go +++ b/structs.go @@ -1,65 +1,31 @@ package ps import ( - "encoding/hex" "encoding/json" "flag" "fmt" + "io/ioutil" "log" + "os" + "path/filepath" + "runtime" "strings" ) -var Colors map[string]Color = map[string]Color{ - "Gray": &RGB{128, 128, 128}, - "White": &RGB{255, 255, 255}, -} - -// Color represents a color. -type Color interface { - RGB() [3]int -} +type ModeEnum int -func Compare(a, b Color) Color { - A := a.RGB() - B := b.RGB() - Aavg := (A[0] + A[1] + A[2]) / 3 - Bavg := (B[0] + B[1] + B[2]) / 3 - if Aavg > Bavg { - return a - } - return b -} +// Mode determines how aggressively ps will attempt to sync with Photoshop. +var Mode ModeEnum -// Color is a color in RGB format. -type RGB struct { - Red int - Green int - Blue int -} +// Normal Mode Always checks to see if layers are updated +// before returning them. +const Normal ModeEnum = 0 -// RGB returns the color in RGB format. -func (r *RGB) RGB() [3]int { - return [3]int{r.Red, r.Green, r.Blue} -} +// Safe Mode Always loads the document from scratch. (Slow) +const Safe ModeEnum = 1 -// Hex is a color in hexidecimal format. -type Hex []uint8 - -func (h Hex) RGB() [3]int { - src := []byte(h) - dst := make([]byte, hex.DecodedLen(len(src))) - _, err := hex.Decode(dst, src) - if err != nil { - panic(err) - } - return [3]int{int(dst[0]), int(dst[1]), int(dst[2])} -} - -// Stroke represents a layer stroke effect. -type Stroke struct { - Size float32 - Color -} +// Fast mode never checks layers before returning. +const Fast ModeEnum = 2 // Group represents a Document or LayerSet. type Group interface { @@ -69,6 +35,8 @@ type Group interface { Path() string ArtLayers() []*ArtLayer LayerSets() []*LayerSet + MarshalJSON() ([]byte, error) + UnmarshalJSON(b []byte) error } // Document represents a Photoshop document (PSD file). @@ -88,6 +56,11 @@ type DocumentJSON struct { LayerSets []*LayerSet } +func (d *Document) MarshalJSON() ([]byte, error) { + return json.Marshal(&DocumentJSON{Name: d.name, Height: d.height, + Width: d.width, ArtLayers: d.artLayers, LayerSets: d.layerSets}) +} + func (d *Document) UnmarshalJSON(b []byte) error { tmp := &DocumentJSON{} if err := json.Unmarshal(b, &tmp); err != nil { @@ -97,7 +70,13 @@ func (d *Document) UnmarshalJSON(b []byte) error { d.height = tmp.Height d.width = tmp.Width d.artLayers = tmp.ArtLayers + for _, lyr := range d.artLayers { + lyr.SetParent(d) + } d.layerSets = tmp.LayerSets + for _, set := range d.layerSets { + set.SetParent(d) + } return nil } @@ -141,10 +120,37 @@ func (d *Document) Path() string { return "" } +// Filename returns the path to the json file for this document. +func (d *Document) Filename() string { + _, dir, _, ok := runtime.Caller(0) + if !ok { + log.Panic("No caller information") + } + return filepath.Join(filepath.Dir(dir), "data", + strings.TrimRight(string(d.name), "\r\n")+".txt") +} + func ActiveDocument() (*Document, error) { - log.Println("Loading ActiveDoucment/") - byt, err := DoJs("getActiveDoc.jsx") - var d *Document + log.Println("Loading ActiveDoucment") + d := &Document{} + + byt, err := DoJs("activeDocName.jsx") + if err != nil { + return nil, err + } + d.name = string(byt) + if Mode != 1 { + byt, err = ioutil.ReadFile(d.Filename()) + if err == nil { + log.Println("Previous version found, loading") + err = json.Unmarshal(byt, &d) + if err == nil { + return d, err + } + } + } + log.Println("Loading manually (This could take awhile)") + byt, err = DoJs("getActiveDoc.jsx") err = json.Unmarshal(byt, &d) for _, lyr := range d.artLayers { lyr.SetParent(d) @@ -155,27 +161,55 @@ func ActiveDocument() (*Document, error) { log.Fatal(err) } d.layerSets[i] = s - // s.SetParent(d) + s.SetParent(d) } + d.Dump() return d, err } +func (d *Document) Dump() { + f, err := os.Create(d.Filename()) + if err != nil { + log.Fatal(err) + } + defer f.Close() + byt, err := json.MarshalIndent(d, "", "\t") + if err != nil { + log.Fatal(err) + } + f.Write(byt) +} + // ArtLayer represents an Art Layer in a photoshop document. type ArtLayer struct { - name string + name string // The layer's name. // TextItem string - bounds [2][2]int - parent Group - visible bool - Color - *Stroke + bounds [2][2]int // The layers' corners. + parent Group // The LayerSet/Document this layer is in. + visible bool // Whether or not the layer is visible. + current bool // Whether we've checked this layer since we loaded from disk. + Color // The layer's color overlay. + *Stroke // The layer's stroke. } type ArtLayerJSON struct { - Name string - Bounds [2][2]int - Parent Group - Visible bool + Name string + Bounds [2][2]int + Visible bool + Color [3]int + Stroke [3]int + StrokeAmt float32 +} + +func (a *ArtLayer) MarshalJSON() ([]byte, error) { + return json.Marshal(&ArtLayerJSON{ + Name: a.name, + Bounds: a.bounds, + Visible: a.visible, + Color: a.Color.RGB(), + Stroke: a.Stroke.RGB(), + StrokeAmt: a.Stroke.Size, + }) } func (a *ArtLayer) UnmarshalJSON(b []byte) error { @@ -185,8 +219,10 @@ func (a *ArtLayer) UnmarshalJSON(b []byte) error { } a.name = tmp.Name a.bounds = tmp.Bounds - a.parent = tmp.Parent + a.Color = RGB{tmp.Color[0], tmp.Color[1], tmp.Color[2]} + a.Stroke = &Stroke{tmp.StrokeAmt, RGB{tmp.Stroke[0], tmp.Stroke[1], tmp.Stroke[2]}} a.visible = tmp.Visible + a.current = false return nil } @@ -231,20 +267,25 @@ func (a *ArtLayer) SetActive() ([]byte, error) { // SetColor creates a color overlay for the layer func (a *ArtLayer) SetColor(c Color) { + if Mode == 2 && a.Color.RGB() == c.RGB() { + return + } + if a.Stroke.Size != 0 { + a.SetStroke(*a.Stroke, c) + return + } a.Color = c - cols := c.RGB() + cols := a.Color.RGB() + log.Printf(`Setting layer "%s" to color %v`, a.name, cols) r := cols[0] g := cols[1] b := cols[2] - if a.Stroke != nil { - a.SetStroke(Stroke{a.Stroke.Size, a.Stroke.Color}, a.Color) - return - } byt, err := a.SetActive() if len(byt) != 0 { log.Println(string(byt), "err") } if err != nil { + log.Println(a.Path()) log.Panic(err) } byt, err = run("colorLayer", fmt.Sprint(r), fmt.Sprint(g), fmt.Sprint(b)) @@ -257,6 +298,13 @@ func (a *ArtLayer) SetColor(c Color) { } func (a *ArtLayer) SetStroke(stk Stroke, fill Color) { + if Mode == 2 { + if stk.Size == a.Stroke.Size && stk.Color.RGB() == a.Color.RGB() { + if a.Color.RGB() == fill.RGB() { + return + } + } + } byt, err := a.SetActive() if len(byt) != 0 { log.Println(string(byt)) @@ -264,8 +312,12 @@ func (a *ArtLayer) SetStroke(stk Stroke, fill Color) { if err != nil { log.Panic(err) } + a.Stroke = &stk + a.Color = fill stkCol := stk.Color.RGB() col := fill.RGB() + log.Printf("Setting layer %s stroke to %.2fpt %v and color to %v\n", a.name, a.Stroke.Size, + a.Stroke.Color.RGB(), a.Color.RGB()) byt, err = run("colorStroke", fmt.Sprint(col[0]), fmt.Sprint(col[1]), fmt.Sprint(col[2]), fmt.Sprintf("%.2f", stk.Size), fmt.Sprint(stkCol[0]), fmt.Sprint(stkCol[1]), fmt.Sprint(stkCol[2])) if len(byt) != 0 { @@ -274,7 +326,6 @@ func (a *ArtLayer) SetStroke(stk Stroke, fill Color) { if err != nil { log.Panic(err) } - } func (a *ArtLayer) Parent() Group { @@ -299,9 +350,16 @@ func Layer(path string) (ArtLayer, error) { // SetVisible makes the layer visible. func (a *ArtLayer) SetVisible(b bool) { + if a.Visible() == b { + return + } + if b { + log.Printf("Showing %s", a.name) + } else { + log.Printf("Hiding %s", a.name) + } js := fmt.Sprintf("%s.visible=%v;", strings.TrimRight(JSLayer(a.Path()), ";"), b) - log.Printf("Setting %s.Visible to %v\n", a.name, b) DoJs("compilejs.jsx", js) } @@ -314,6 +372,9 @@ func (a *ArtLayer) Visible() bool { // Valid options for bound are: TL, TR, BL, BR // TODO: Improve func (a *ArtLayer) SetPos(x, y int, bound string) { + if x == 0 && y == 0 { + return + } if !a.visible { return } @@ -326,35 +387,51 @@ func (a *ArtLayer) SetPos(x, y int, bound string) { lyrY = a.Y1() } byt, err := DoJs("moveLayer.jsx", JSLayer(a.Path()), fmt.Sprint(x-lyrX), fmt.Sprint(y-lyrY)) + var bounds [2][2]int if err != nil { panic(err) } - json.Unmarshal(byt, &a) + json.Unmarshal(byt, bounds) + a.bounds = bounds } type LayerSet struct { name string parent Group + current bool // Whether we've checked this layer since we loaded from disk. artLayers []*ArtLayer layerSets []*LayerSet } type LayerSetJSON struct { Name string - Parent Group ArtLayers []*ArtLayer LayerSets []*LayerSet } +func (l *LayerSet) MarshalJSON() ([]byte, error) { + return json.Marshal(&LayerSetJSON{ + Name: l.name, + ArtLayers: l.artLayers, + LayerSets: l.layerSets, + }) +} + func (l *LayerSet) UnmarshalJSON(b []byte) error { tmp := &LayerSetJSON{} if err := json.Unmarshal(b, &tmp); err != nil { return err } l.name = tmp.Name - l.parent = tmp.Parent l.artLayers = tmp.ArtLayers + for _, lyr := range l.artLayers { + lyr.SetParent(l) + } l.layerSets = tmp.LayerSets + for _, set := range l.layerSets { + set.SetParent(l) + } + l.current = false return nil } @@ -363,6 +440,11 @@ func (l *LayerSet) Name() string { } func (l *LayerSet) ArtLayers() []*ArtLayer { + for i := 0; i < len(l.artLayers); i++ { + if l.artLayers[i] != nil && !l.artLayers[i].current { + l.artLayers[i] = l.ArtLayer(l.artLayers[i].name) + } + } return l.artLayers } @@ -371,6 +453,18 @@ func (l *LayerSet) ArtLayers() []*ArtLayer { func (l *LayerSet) ArtLayer(name string) *ArtLayer { for _, lyr := range l.artLayers { if lyr.name == name { + if Mode == 0 && !lyr.current { + byt, err := DoJs("getLayer.jsx", JSLayer(lyr.Path())) + if err != nil { + log.Panic(err) + } + var lyr2 *ArtLayer + err = json.Unmarshal(byt, &lyr2) + lyr.name = lyr2.name + lyr.bounds = lyr2.bounds + lyr.visible = lyr2.visible + lyr.current = true + } return lyr } } @@ -408,11 +502,13 @@ func (l *LayerSet) Path() string { } func NewLayerSet(path string, g Group) (*LayerSet, error) { + path = strings.Replace(path, "//", "/", -1) byt, err := DoJs("getLayerSet.jsx", JSLayer(path)) var out *LayerSet err = json.Unmarshal(byt, &out) if flag.Lookup("test.v") != nil { - // log.Println(string(byt)) + // log.Println(path) + // log.Println(out) } out.SetParent(g) log.Printf("Loading ActiveDocument/%s\n", out.Path()) @@ -423,7 +519,6 @@ func NewLayerSet(path string, g Group) (*LayerSet, error) { lyr.SetParent(out) } for i, set := range out.layerSets { - // log.Println("\t", set.name) s, err := NewLayerSet(fmt.Sprintf("%s%s/", path, set.Name()), out) if err != nil { log.Fatal(err) From ff38043a109839ca4db19221ce7fb9385276e8d9 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 23 Mar 2018 15:26:33 -0400 Subject: [PATCH 16/22] Improved documentation all colors must now implement hex notation as well as rgb --- colors.go | 22 ++++++++++++++++++---- ps.go | 40 +++++++++++++++++++++++----------------- structs.go | 45 +++++++++++++++++++++++++++++---------------- 3 files changed, 70 insertions(+), 37 deletions(-) diff --git a/colors.go b/colors.go index c371a05..6a4e311 100644 --- a/colors.go +++ b/colors.go @@ -9,11 +9,16 @@ var Colors map[string]Color = map[string]Color{ "White": &RGB{255, 255, 255}, } -// Color represents a color. +// Color is an interface for color objects, allowing colors to be +// used in various formats. +// +// RGB is the default format for everything. type Color interface { - RGB() [3]int + RGB() [3]int // The color in RGB format. + Hex() []uint8 // The color in hexadecimal format. } +// Compare determines which of two colors is "brighter". func Compare(a, b Color) Color { A := a.RGB() B := b.RGB() @@ -25,7 +30,7 @@ func Compare(a, b Color) Color { return b } -// Color is a color in RGB format. +// RGB is a color in RGB format. It fulfills the Color interface. type RGB struct { Red int Green int @@ -37,7 +42,12 @@ func (r RGB) RGB() [3]int { return [3]int{r.Red, r.Green, r.Blue} } -// Hex is a color in hexidecimal format. +// TODO: Implement RGB.Hex() +func (r RGB) Hex() []uint8 { + return make([]uint8, 6) +} + +// Hex is a color in hexadecimal format. It fulfills the Color interface. type Hex []uint8 func (h Hex) RGB() [3]int { @@ -50,6 +60,10 @@ func (h Hex) RGB() [3]int { return [3]int{int(dst[0]), int(dst[1]), int(dst[2])} } +func (h Hex) Hex() []uint8 { + return h +} + // Stroke represents a layer stroke effect. type Stroke struct { Size float32 diff --git a/ps.go b/ps.go index 406b65b..fd49357 100644 --- a/ps.go +++ b/ps.go @@ -1,7 +1,7 @@ -// Package ps is a rudimentary API between Adobe Photoshop and go. +// Package ps is a rudimentary API between Adobe Photoshop CS5 and Golang. // // Most of the interaction between the two is implemented via -// javascript and/or VBS/Applescript. +// Javascript and/or VBS/Applescript. // // Currently only works with CS5 on Windows. package ps @@ -54,30 +54,41 @@ func Start() error { return err } -// Close closes the active document. +// Close closes the active document in Photoshop. func Close(save PSSaveOptions) error { _, err := run("close", save.String()) return err } -// Open opens a file with the specified path. +// Open opens a Photoshop document with the specified path. +// If Photoshop is not currently running, it is started before +// opening the document func Open(path string) error { _, err := run("open", path) return err } -// Quit exits Photoshop. +// Quit exits Photoshop with the given saving option. func Quit(save PSSaveOptions) error { _, err := run("quit", save.String()) return err } -// DoJs runs a Photoshop javascript script file (.jsx) from the specified location. +// SaveAs saves the Photoshop document to the given location. +func SaveAs(path string) error { + _, err := run("save", path) + return err +} + +// DoJs runs a Photoshop Javascript script file (.jsx) from the specified location. // It can't directly return output, so instead the scripts write their output to -// a temporary file. +// a temporary file, whose contents is then read and returned. func DoJs(path string, args ...string) (out []byte, err error) { // Temp file for js to output to. outpath := filepath.Join(os.Getenv("TEMP"), "js_out.txt") + if !strings.HasSuffix(path, ".jsx") { + path += ".jsx" + } defer os.Remove(outpath) args = append([]string{outpath}, args...) @@ -150,18 +161,12 @@ func run(name string, args ...string) ([]byte, error) { return out.Bytes(), nil } -// DoAction runs a Photoshop action with name from set. +// DoAction runs the Photoshop action with name from set. func DoAction(set, name string) error { _, err := run("action", set, name) return err } -// SaveAs saves the Photoshop document file to the given location. -func SaveAs(path string) error { - _, err := run("save", path) - return err -} - // Layers returns an array of ArtLayers from the active document // based on the given path string. /*func Layers(path string) ([]ArtLayer, error) { @@ -172,15 +177,16 @@ func SaveAs(path string) error { return []ArtLayer{}, err } return out, err -} -*/ +}*/ // ApplyDataset fills out a template file with information from a given dataset (csv) file. func ApplyDataset(name string) ([]byte, error) { return DoJs("applyDataset.jsx", name) } -// JSLayer retrurns javascript code to get the layer with a given path. +// JSLayer "compiles" Javascript code to get an ArtLayer with the given path. +// The output always ends with a semicolon, so if you want to access a specific +// property of the layer, you'll have to trim the output before concatenating func JSLayer(path string) string { path = strings.TrimLeft(path, "/") pth := strings.Split(path, "/") diff --git a/structs.go b/structs.go index 3d4eead..c7e3465 100644 --- a/structs.go +++ b/structs.go @@ -180,7 +180,7 @@ func (d *Document) Dump() { f.Write(byt) } -// ArtLayer represents an Art Layer in a photoshop document. +// ArtLayer reflects certain values from an Art Layer in a Photoshop document. type ArtLayer struct { name string // The layer's name. // TextItem string @@ -192,6 +192,14 @@ type ArtLayer struct { *Stroke // The layer's stroke. } +// Bounds returns the furthest corners of the ArtLayer. +func (a *ArtLayer) Bounds() [2][2]int { + return a.bounds +} + +// ArtLayerJSON is a bridge between the ArtLayer struct and +// the encoding/json package, allowing ArtLayer's unexported fields +// to ber written to and read from by the json package. type ArtLayerJSON struct { Name string Bounds [2][2]int @@ -201,6 +209,8 @@ type ArtLayerJSON struct { StrokeAmt float32 } +// MarshalJSON fufills the json.Marshaler interface, allowing the ArtLayer to be +// saved to disk in JSON format. func (a *ArtLayer) MarshalJSON() ([]byte, error) { return json.Marshal(&ArtLayerJSON{ Name: a.name, @@ -230,10 +240,6 @@ func (a *ArtLayer) Name() string { return a.name } -func (a *ArtLayer) Bounds() [2][2]int { - return a.bounds -} - // X1 returns the layer's leftmost x value. func (a *ArtLayer) X1() int { return a.bounds[0][0] @@ -244,7 +250,7 @@ func (a *ArtLayer) X2() int { return a.bounds[1][0] } -// Y1 returns the layer's topmost Y value. +// Y1 returns the layer's topmost y value. func (a *ArtLayer) Y1() int { return a.bounds[0][1] } @@ -369,23 +375,30 @@ func (a *ArtLayer) Visible() bool { } // SetPos snaps the given layer boundry to the given point. -// Valid options for bound are: TL, TR, BL, BR -// TODO: Improve +// Valid options for bound are: "TL", "TR", "BL", "BR" +// +// TODO: Test TR and BR func (a *ArtLayer) SetPos(x, y int, bound string) { - if x == 0 && y == 0 { - return - } - if !a.visible { + if !a.visible || (x == 0 && y == 0) { return } - var lyrX, lyrY int - lyrX = a.X1() - if bound != "TL" { + switch bound[:1] { + case "B": lyrY = a.Y2() - } else { // "BL" + case "T": + fallthrough + default: lyrY = a.Y1() } + switch bound[1:] { + case "R": + lyrX = a.X2() + case "L": + fallthrough + default: + lyrX = a.X1() + } byt, err := DoJs("moveLayer.jsx", JSLayer(a.Path()), fmt.Sprint(x-lyrX), fmt.Sprint(y-lyrY)) var bounds [2][2]int if err != nil { From 669d1182f47650f8698dcf55496efb35fda212fc Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 29 Mar 2018 14:30:42 -0400 Subject: [PATCH 17/22] Got text working. --- Variables.go | 43 ++++++++++++++++++++++++++++++++++++++++ cmd/ps/main.go | 27 ------------------------- colors.go | 5 ----- ps.go | 13 ------------ ps_test.go | 2 ++ scripts/getActiveDoc.jsx | 13 ++++++++---- scripts/getLayer.jsx | 10 ++++++++-- scripts/getLayerSet.jsx | 7 ++++++- structs.go | 38 +++++++++++++++++------------------ 9 files changed, 87 insertions(+), 71 deletions(-) create mode 100644 Variables.go delete mode 100644 cmd/ps/main.go diff --git a/Variables.go b/Variables.go new file mode 100644 index 0000000..f5f1c58 --- /dev/null +++ b/Variables.go @@ -0,0 +1,43 @@ +package ps + +import ( + "fmt" +) + +var Colors map[string]Color = map[string]Color{ + "Gray": &RGB{128, 128, 128}, + "White": &RGB{255, 255, 255}, +} + +// ModeEnum determines how aggressively the package will attempt to sync with Photoshop. +type ModeEnum int + +// Holds the current mode. +var Mode ModeEnum + +// Fast mode never checks layers before returning. +const Fast ModeEnum = 2 + +// Normal Mode Always checks to see if layers are up to date +// before returning them. +const Normal ModeEnum = 0 + +// Safe Mode Always loads the document from scratch. (Very Slow) +const Safe ModeEnum = 1 + +// PSSaveOptions is an enum for options when closing a document. +type PSSaveOptions int + +func (p *PSSaveOptions) String() string { + return fmt.Sprint("", *p) +} + +// PSSaveChanges saves changes before closing documents. +const PSSaveChanges PSSaveOptions = 1 + +// PSDoNotSaveChanges closes documents without saving. +const PSDoNotSaveChanges PSSaveOptions = 2 + +// PSPromptToSaveChanges prompts the user whether to save each +// document before closing it. +const PSPromptToSaveChanges PSSaveOptions = 3 diff --git a/cmd/ps/main.go b/cmd/ps/main.go deleted file mode 100644 index b1d0fa9..0000000 --- a/cmd/ps/main.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import ( - "fmt" - "github.com/sbrow/ps" - "os" -) - -func main() { - args := []string{} - cmd := "" - switch { - case len(os.Args) > 1: - args = os.Args[2:] - fallthrough - case len(os.Args) > 0: - cmd = os.Args[1] - } - - fmt.Println(os.Args, cmd, args) - if cmd == "action" { - err := ps.DoAction(args[0], args[1]) - if err != nil { - panic(err) - } - } -} diff --git a/colors.go b/colors.go index 6a4e311..84126cf 100644 --- a/colors.go +++ b/colors.go @@ -4,11 +4,6 @@ import ( "encoding/hex" ) -var Colors map[string]Color = map[string]Color{ - "Gray": &RGB{128, 128, 128}, - "White": &RGB{255, 255, 255}, -} - // Color is an interface for color objects, allowing colors to be // used in various formats. // diff --git a/ps.go b/ps.go index fd49357..44c3411 100644 --- a/ps.go +++ b/ps.go @@ -23,19 +23,6 @@ var Cmd string var Opts string var pkgpath string -// PSSaveOptions is an enum for options when closing a document. -type PSSaveOptions int - -func (p *PSSaveOptions) String() string { - return fmt.Sprint("", *p) -} - -const ( - PSSaveChanges PSSaveOptions = 1 - PSDoNotSaveChanges PSSaveOptions = 2 - PSPromptToSaveChanges PSSaveOptions = 3 -) - func init() { _, file, _, _ := runtime.Caller(0) pkgpath = filepath.Dir(file) diff --git a/ps_test.go b/ps_test.go index 34e293b..04ba17d 100644 --- a/ps_test.go +++ b/ps_test.go @@ -137,6 +137,7 @@ func TestMove(t *testing.T) { } func TestActiveDocument(t *testing.T) { + Mode = Safe if testing.Short() { t.Skip("Skipping \"TestDocument\"") } @@ -167,6 +168,7 @@ func TestActiveDocument(t *testing.T) { } s := Stroke{Size: 4, Color: &RGB{0, 0, 0}} lyr.SetStroke(s, &RGB{128, 128, 128}) + d.Dump() } func TestColor(t *testing.T) { diff --git a/scripts/getActiveDoc.jsx b/scripts/getActiveDoc.jsx index eb14b4e..fbcfb7d 100644 --- a/scripts/getActiveDoc.jsx +++ b/scripts/getActiveDoc.jsx @@ -10,10 +10,15 @@ function layers(lyrs) { var lyr = lyrs[i]; stdout.write(('{"Name":"' + lyr.name + '", "Bounds": [[' + lyr.bounds[0] + ',' + lyr.bounds[1] + '],[' + lyr.bounds[2] + ',' + - lyr.bounds[3] + ']], "Visible": ' + lyr.visible + '}').replace(/ px/g, "")); - if (i+1 != lyrs.length) - stdout.write(','); - stdout.writeln(); + lyr.bounds[3] + ']], "Visible": ' + lyr.visible+',"Text":').replace(/ px/g, "")); + if (lyr.kind == LayerKind.TEXT) + stdout.write('"'+lyr.textItem.contents+'"'); + else + stdout.write("null"); + stdout.write("}") + if (i+1 != lyrs.length) + stdout.write(','); + stdout.writeln(); } } layers(doc.artLayers) diff --git a/scripts/getLayer.jsx b/scripts/getLayer.jsx index ac439ea..66cbfd5 100644 --- a/scripts/getLayer.jsx +++ b/scripts/getLayer.jsx @@ -2,7 +2,13 @@ var stdout = newFile(arguments[0]); var lyr = eval(arguments[1]); -stdout.writeln(('{"Name":"' + lyr.name + '","Bounds":[[' + lyr.bounds[0] + ',' + +stdout.write(('{"Name":"' + lyr.name + '","Bounds":[[' + lyr.bounds[0] + ',' + lyr.bounds[1] + '],[' + lyr.bounds[2] + ',' + - lyr.bounds[3] + ']],"Visible":' + lyr.visible + '}').replace(/ px/g, "")); + lyr.bounds[3] + ']],"Visible":' + lyr.visible+',"Text":').replace(/ px/g, "")); +if (lyr.kind == LayerKind.TEXT) { + stdout.write('"'+lyr.textItem.contents.replace(/\r/g, "\\r")+'"'); +} +else + stdout.write(null) +stdout.writeln('}') stdout.close(); \ No newline at end of file diff --git a/scripts/getLayerSet.jsx b/scripts/getLayerSet.jsx index c0eae16..289d9b9 100644 --- a/scripts/getLayerSet.jsx +++ b/scripts/getLayerSet.jsx @@ -7,7 +7,12 @@ for (var i = 0; i < set.artLayers.length; i++) { var lyr = set.artLayers[i]; stdout.write(('{"Name":"' + lyr.name + '", "Bounds": [[' + lyr.bounds[0] + ',' + lyr.bounds[1] + '],[' + lyr.bounds[2] + ',' + - lyr.bounds[3] + ']], "Visible": ' + lyr.visible + '}').replace(/ px/g, "")); + lyr.bounds[3] + ']], "Visible": ' + lyr.visible + ',"Text":').replace(/ px/g, "")); + if (lyr.kind == LayerKind.TEXT) + stdout.write('"'+lyr.textItem.contents.replace(/\r/g, "\\r")+'"'); + else + stdout.write("null"); + stdout.write("}") if (i != set.artLayers.length - 1) stdout.writeln(","); } diff --git a/structs.go b/structs.go index c7e3465..9d57bce 100644 --- a/structs.go +++ b/structs.go @@ -12,21 +12,6 @@ import ( "strings" ) -type ModeEnum int - -// Mode determines how aggressively ps will attempt to sync with Photoshop. -var Mode ModeEnum - -// Normal Mode Always checks to see if layers are updated -// before returning them. -const Normal ModeEnum = 0 - -// Safe Mode Always loads the document from scratch. (Slow) -const Safe ModeEnum = 1 - -// Fast mode never checks layers before returning. -const Fast ModeEnum = 2 - // Group represents a Document or LayerSet. type Group interface { Name() string @@ -182,8 +167,8 @@ func (d *Document) Dump() { // ArtLayer reflects certain values from an Art Layer in a Photoshop document. type ArtLayer struct { - name string // The layer's name. - // TextItem string + name string // The layer's name. + Text *string // The contents of a text layer. bounds [2][2]int // The layers' corners. parent Group // The LayerSet/Document this layer is in. visible bool // Whether or not the layer is visible. @@ -207,11 +192,13 @@ type ArtLayerJSON struct { Color [3]int Stroke [3]int StrokeAmt float32 + Text *string } -// MarshalJSON fufills the json.Marshaler interface, allowing the ArtLayer to be +// MarshalJSON fulfills the json.Marshaler interface, allowing the ArtLayer to be // saved to disk in JSON format. func (a *ArtLayer) MarshalJSON() ([]byte, error) { + // txt := strings.Replace(*a.Text, "\r", "\\r", -1) return json.Marshal(&ArtLayerJSON{ Name: a.name, Bounds: a.bounds, @@ -219,6 +206,7 @@ func (a *ArtLayer) MarshalJSON() ([]byte, error) { Color: a.Color.RGB(), Stroke: a.Stroke.RGB(), StrokeAmt: a.Stroke.Size, + Text: a.Text, }) } @@ -232,6 +220,10 @@ func (a *ArtLayer) UnmarshalJSON(b []byte) error { a.Color = RGB{tmp.Color[0], tmp.Color[1], tmp.Color[2]} a.Stroke = &Stroke{tmp.StrokeAmt, RGB{tmp.Stroke[0], tmp.Stroke[1], tmp.Stroke[2]}} a.visible = tmp.Visible + if tmp.Text != nil { + // s := strings.Replace(*tmp.Text, "\\r", "\r", -1) + a.Text = tmp.Text + } a.current = false return nil } @@ -264,7 +256,7 @@ func (a *ArtLayer) SetParent(c Group) { a.parent = c } -// SetActive makes this layer active in photoshop. +// SetActive makes this layer active in Photoshop. // Layers need to be active to perform certain operations func (a *ArtLayer) SetActive() ([]byte, error) { js := fmt.Sprintf("app.activeDocument.activeLayer=%s", JSLayer(a.Path())) @@ -473,10 +465,14 @@ func (l *LayerSet) ArtLayer(name string) *ArtLayer { } var lyr2 *ArtLayer err = json.Unmarshal(byt, &lyr2) + if err != nil { + log.Panic(err) + } lyr.name = lyr2.name lyr.bounds = lyr2.bounds lyr.visible = lyr2.visible lyr.current = true + lyr.Text = lyr2.Text } return lyr } @@ -519,6 +515,10 @@ func NewLayerSet(path string, g Group) (*LayerSet, error) { byt, err := DoJs("getLayerSet.jsx", JSLayer(path)) var out *LayerSet err = json.Unmarshal(byt, &out) + if err != nil { + log.Println(string(byt)) + log.Panic(err) + } if flag.Lookup("test.v") != nil { // log.Println(path) // log.Println(out) From d52e896ac317e3291ffbb71c01b17120eefb837e Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 3 Apr 2018 16:56:12 -0400 Subject: [PATCH 18/22] Added Refresh function to update layers * Fixed functions that were mode dependant. * Fixed getLayerSet.jsx to return set visibility. --- colors.go | 5 ++ ps.go | 18 ++----- scripts/getLayerSet.jsx | 2 +- structs.go | 117 ++++++++++++++++++++++++++++------------ 4 files changed, 95 insertions(+), 47 deletions(-) diff --git a/colors.go b/colors.go index 84126cf..ec9fba9 100644 --- a/colors.go +++ b/colors.go @@ -2,6 +2,7 @@ package ps import ( "encoding/hex" + // "fmt" ) // Color is an interface for color objects, allowing colors to be @@ -64,3 +65,7 @@ type Stroke struct { Size float32 Color } + +// func (s *Stroke) String() string { +// return fmt.Sprintf("%vpt %v", s.Size, s.Color.RGB()) +// } diff --git a/ps.go b/ps.go index 44c3411..9c9d773 100644 --- a/ps.go +++ b/ps.go @@ -154,19 +154,11 @@ func DoAction(set, name string) error { return err } -// Layers returns an array of ArtLayers from the active document -// based on the given path string. -/*func Layers(path string) ([]ArtLayer, error) { - byt, err := DoJs("getLayers.jsx", JSLayer(path)) - var out []ArtLayer - err = json.Unmarshal(byt, &out) - if err != nil { - return []ArtLayer{}, err - } - return out, err -}*/ - -// ApplyDataset fills out a template file with information from a given dataset (csv) file. +// ApplyDataset fills out a template file with information +// from a given dataset (csv) file. It is important to note that running this +// function will change data in the Photoshop document, but will not update +// data in the Go Document struct (if any); you will have to implement syncing +// them yourself. func ApplyDataset(name string) ([]byte, error) { return DoJs("applyDataset.jsx", name) } diff --git a/scripts/getLayerSet.jsx b/scripts/getLayerSet.jsx index 289d9b9..313618a 100644 --- a/scripts/getLayerSet.jsx +++ b/scripts/getLayerSet.jsx @@ -19,7 +19,7 @@ for (var i = 0; i < set.artLayers.length; i++) { stdout.write('], "LayerSets": [') for (var i = 0; i < set.layerSets.length; i++) { var s = set.layerSets[i]; - stdout.write('{"Name": "'+ s.name +'"}'); + stdout.write('{"Name": "'+ s.name +'", "Visible": '+s.visible+'}'); if (i < set.layerSets.length - 1) stdout.writeln(","); } diff --git a/structs.go b/structs.go index 9d57bce..6648746 100644 --- a/structs.go +++ b/structs.go @@ -2,7 +2,6 @@ package ps import ( "encoding/json" - "flag" "fmt" "io/ioutil" "log" @@ -153,6 +152,7 @@ func ActiveDocument() (*Document, error) { } func (d *Document) Dump() { + log.Println("Dumping to disk") f, err := os.Create(d.Filename()) if err != nil { log.Fatal(err) @@ -265,8 +265,11 @@ func (a *ArtLayer) SetActive() ([]byte, error) { // SetColor creates a color overlay for the layer func (a *ArtLayer) SetColor(c Color) { - if Mode == 2 && a.Color.RGB() == c.RGB() { - return + if a.Color.RGB() == c.RGB() { + if Mode == 2 || (Mode == 0 && a.current) { + log.Println("Skipping color: already set.") + return + } } if a.Stroke.Size != 0 { a.SetStroke(*a.Stroke, c) @@ -296,9 +299,10 @@ func (a *ArtLayer) SetColor(c Color) { } func (a *ArtLayer) SetStroke(stk Stroke, fill Color) { - if Mode == 2 { - if stk.Size == a.Stroke.Size && stk.Color.RGB() == a.Color.RGB() { - if a.Color.RGB() == fill.RGB() { + if stk.Size == a.Stroke.Size && stk.Color.RGB() == a.Stroke.Color.RGB() { + if a.Color.RGB() == fill.RGB() { + if Mode == 2 || (Mode == 0 && a.current) { + log.Println("Skipping stroke: already set.") return } } @@ -310,6 +314,8 @@ func (a *ArtLayer) SetStroke(stk Stroke, fill Color) { if err != nil { log.Panic(err) } + log.Printf(" layer %s stroke was %.2fpt %v and color to %v\n", a.name, a.Stroke.Size, + a.Stroke.Color.RGB(), a.Color.RGB()) a.Stroke = &stk a.Color = fill stkCol := stk.Color.RGB() @@ -348,12 +354,14 @@ func Layer(path string) (ArtLayer, error) { // SetVisible makes the layer visible. func (a *ArtLayer) SetVisible(b bool) { - if a.Visible() == b { + if a.visible == b { return } - if b { + a.visible = b + switch b { + case true: log.Printf("Showing %s", a.name) - } else { + case false: log.Printf("Hiding %s", a.name) } js := fmt.Sprintf("%s.visible=%v;", @@ -392,18 +400,36 @@ func (a *ArtLayer) SetPos(x, y int, bound string) { lyrX = a.X1() } byt, err := DoJs("moveLayer.jsx", JSLayer(a.Path()), fmt.Sprint(x-lyrX), fmt.Sprint(y-lyrY)) - var bounds [2][2]int if err != nil { panic(err) } - json.Unmarshal(byt, bounds) - a.bounds = bounds + var lyr ArtLayer + err = json.Unmarshal(byt, &lyr) + if err != nil { + log.Panic(err) + } + a.bounds = lyr.bounds +} + +func (a *ArtLayer) Refresh() { + tmp, err := Layer(a.Path()) + if err != nil { + log.Panic(err) + } + tmp.SetParent(a.Parent()) + a.name = tmp.name + a.bounds = tmp.bounds + a.Text = tmp.Text + a.parent = tmp.Parent() + a.visible = tmp.visible + a.current = true } type LayerSet struct { name string parent Group current bool // Whether we've checked this layer since we loaded from disk. + visible bool artLayers []*ArtLayer layerSets []*LayerSet } @@ -445,9 +471,11 @@ func (l *LayerSet) Name() string { } func (l *LayerSet) ArtLayers() []*ArtLayer { - for i := 0; i < len(l.artLayers); i++ { - if l.artLayers[i] != nil && !l.artLayers[i].current { - l.artLayers[i] = l.ArtLayer(l.artLayers[i].name) + if Mode != 2 { + for _, lyr := range l.artLayers { + if !lyr.current { + lyr.Refresh() + } } } return l.artLayers @@ -459,20 +487,23 @@ func (l *LayerSet) ArtLayer(name string) *ArtLayer { for _, lyr := range l.artLayers { if lyr.name == name { if Mode == 0 && !lyr.current { - byt, err := DoJs("getLayer.jsx", JSLayer(lyr.Path())) - if err != nil { - log.Panic(err) - } - var lyr2 *ArtLayer - err = json.Unmarshal(byt, &lyr2) - if err != nil { - log.Panic(err) - } - lyr.name = lyr2.name - lyr.bounds = lyr2.bounds - lyr.visible = lyr2.visible - lyr.current = true - lyr.Text = lyr2.Text + lyr.Refresh() + /* + byt, err := DoJs("getLayer.jsx", JSLayer(lyr.Path())) + if err != nil { + log.Panic(err) + } + var lyr2 *ArtLayer + err = json.Unmarshal(byt, &lyr2) + if err != nil { + log.Panic(err) + } + lyr.name = lyr2.name + lyr.bounds = lyr2.bounds + lyr.visible = lyr2.visible + lyr.current = true + lyr.Text = lyr2.Text + */ } return lyr } @@ -519,10 +550,6 @@ func NewLayerSet(path string, g Group) (*LayerSet, error) { log.Println(string(byt)) log.Panic(err) } - if flag.Lookup("test.v") != nil { - // log.Println(path) - // log.Println(out) - } out.SetParent(g) log.Printf("Loading ActiveDocument/%s\n", out.Path()) if err != nil { @@ -544,6 +571,30 @@ func NewLayerSet(path string, g Group) (*LayerSet, error) { // SetVisible makes the LayerSet visible. func (l *LayerSet) SetVisible(b bool) { + if l.visible == b { + return + } + l.visible = b js := fmt.Sprintf("%s%v", JSLayer(strings.TrimRight(l.Path(), ";")), b) DoJs("compilejs.jsx", js) } + +func (l *LayerSet) Refresh() { + var tmp *LayerSet + byt, err := DoJs("getLayerSet.jsx", JSLayer(l.Path())) + err = json.Unmarshal(byt, &tmp) + if err != nil { + log.Println(string(byt)) + log.Panic(err) + } + tmp.SetParent(l.Parent()) + for _, lyr := range l.artLayers { + lyr.Refresh() + } + for _, set := range l.layerSets { + set.Refresh() + } + l.name = tmp.name + l.visible = tmp.visible + l.current = true +} From 4bf7eca6b0134263d756925408379a43304f18ab Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 4 Apr 2018 15:14:59 -0400 Subject: [PATCH 19/22] Minor Improvements * Made Refresh() more robust - when an error is encountered, the layer(set) is reloaded automatically. * JS errors are now output to console instead of alerts. --- ps.go | 20 +++++++------------- scripts/PsIsOpen.vbs | 12 ++++++++++++ scripts/dojs.vbs | 5 ++++- scripts/getLayer.jsx | 2 +- structs.go | 39 ++++++++++++++++++--------------------- 5 files changed, 42 insertions(+), 36 deletions(-) create mode 100644 scripts/PsIsOpen.vbs diff --git a/ps.go b/ps.go index 9c9d773..2450b1d 100644 --- a/ps.go +++ b/ps.go @@ -73,10 +73,10 @@ func SaveAs(path string) error { func DoJs(path string, args ...string) (out []byte, err error) { // Temp file for js to output to. outpath := filepath.Join(os.Getenv("TEMP"), "js_out.txt") + defer os.Remove(outpath) if !strings.HasSuffix(path, ".jsx") { path += ".jsx" } - defer os.Remove(outpath) args = append([]string{outpath}, args...) @@ -87,14 +87,12 @@ func DoJs(path string, args ...string) (out []byte, err error) { args = append([]string{path}, args...) cmd, err := run("dojs", args...) - if err != nil { - return []byte{}, err - } - file, err := ioutil.ReadFile(outpath) - if err != nil { - return cmd, err + if err == nil { + file, err := ioutil.ReadFile(outpath) + if err == nil { + cmd = append(cmd, file...) + } } - cmd = append(cmd, file...) return cmd, err } @@ -138,11 +136,7 @@ func run(name string, args ...string) ([]byte, error) { cmd.Stdout = &out cmd.Stderr = &errs err := cmd.Run() - if err != nil { - return out.Bytes(), err - // return append(out.Bytes(), errs.Bytes()...), err - } - if len(errs.Bytes()) != 0 { + if err != nil || len(errs.Bytes()) != 0 { return out.Bytes(), errors.New(string(errs.Bytes())) } return out.Bytes(), nil diff --git a/scripts/PsIsOpen.vbs b/scripts/PsIsOpen.vbs new file mode 100644 index 0000000..2e44e2f --- /dev/null +++ b/scripts/PsIsOpen.vbs @@ -0,0 +1,12 @@ +Function IsProcessRunning( strComputer, strProcess ) + Dim Process, strObject + IsProcessRunning = False + strObject = "winmgmts://" & strComputer + For Each Process in GetObject( strObject ).InstancesOf( "win32_process" ) + If UCase( Process.name ) = UCase( strProcess ) Then + IsProcessRunning = True + Exit Function + End If + Next +End Function +wScript.Echo IsProcessRunning(".", "Photoshop.exe") diff --git a/scripts/dojs.vbs b/scripts/dojs.vbs index 6819bfd..a4b0311 100644 --- a/scripts/dojs.vbs +++ b/scripts/dojs.vbs @@ -6,5 +6,8 @@ if wScript.Arguments.Count = 0 then else path = wScript.Arguments(0) args = wScript.Arguments(1) - appRef.DoJavaScriptFile path, Split(args, ",") + error = appRef.DoJavaScriptFile(path, Split(args, ",")) + if Not error = "true" Then + Err.raise 1, "dojs.vbs", error + end if end if \ No newline at end of file diff --git a/scripts/getLayer.jsx b/scripts/getLayer.jsx index 66cbfd5..0375de8 100644 --- a/scripts/getLayer.jsx +++ b/scripts/getLayer.jsx @@ -1,5 +1,5 @@ #include lib.js - +app.displayDialogs=DialogModes.NO var stdout = newFile(arguments[0]); var lyr = eval(arguments[1]); stdout.write(('{"Name":"' + lyr.name + '","Bounds":[[' + lyr.bounds[0] + ',' + diff --git a/structs.go b/structs.go index 6648746..fa18621 100644 --- a/structs.go +++ b/structs.go @@ -411,10 +411,10 @@ func (a *ArtLayer) SetPos(x, y int, bound string) { a.bounds = lyr.bounds } -func (a *ArtLayer) Refresh() { +func (a *ArtLayer) Refresh() error { tmp, err := Layer(a.Path()) if err != nil { - log.Panic(err) + return err } tmp.SetParent(a.Parent()) a.name = tmp.name @@ -423,6 +423,7 @@ func (a *ArtLayer) Refresh() { a.parent = tmp.Parent() a.visible = tmp.visible a.current = true + return nil } type LayerSet struct { @@ -487,28 +488,20 @@ func (l *LayerSet) ArtLayer(name string) *ArtLayer { for _, lyr := range l.artLayers { if lyr.name == name { if Mode == 0 && !lyr.current { - lyr.Refresh() - /* - byt, err := DoJs("getLayer.jsx", JSLayer(lyr.Path())) - if err != nil { - log.Panic(err) - } - var lyr2 *ArtLayer - err = json.Unmarshal(byt, &lyr2) - if err != nil { - log.Panic(err) - } - lyr.name = lyr2.name - lyr.bounds = lyr2.bounds - lyr.visible = lyr2.visible - lyr.current = true - lyr.Text = lyr2.Text - */ + err := lyr.Refresh() + if err != nil { + l.Refresh() + } } return lyr } } - return nil + l.Refresh() + for _, lyr := range l.artLayers { + fmt.Println(lyr) + } + lyr := l.ArtLayer(name) + return lyr } func (l *LayerSet) LayerSets() []*LayerSet { @@ -589,7 +582,11 @@ func (l *LayerSet) Refresh() { } tmp.SetParent(l.Parent()) for _, lyr := range l.artLayers { - lyr.Refresh() + err := lyr.Refresh() + if err != nil { + l.artLayers = tmp.artLayers + break + } } for _, set := range l.layerSets { set.Refresh() From e90c491450fc79048279745093813c8102d111e7 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 12 Apr 2018 13:07:56 -0400 Subject: [PATCH 20/22] Text can now be formatted - Added black to the colors - Fixed setStroke to be skipped when needed --- Variables.go | 1 + scripts/dojs.vbs | 2 +- scripts/fmtText.jsx | 54 ++++++++++++++++++++++++++++++++++++++ scripts/lib.js | 64 +++++++++++++++++++++++++++++++++++++++++++++ scripts/test.jsx | 2 ++ structs.go | 43 +++++++++++++++++++++++------- 6 files changed, 156 insertions(+), 10 deletions(-) create mode 100644 scripts/fmtText.jsx diff --git a/Variables.go b/Variables.go index f5f1c58..ce6d7e1 100644 --- a/Variables.go +++ b/Variables.go @@ -5,6 +5,7 @@ import ( ) var Colors map[string]Color = map[string]Color{ + "Black": &RGB{0, 0, 0}, "Gray": &RGB{128, 128, 128}, "White": &RGB{255, 255, 255}, } diff --git a/scripts/dojs.vbs b/scripts/dojs.vbs index a4b0311..997320b 100644 --- a/scripts/dojs.vbs +++ b/scripts/dojs.vbs @@ -7,7 +7,7 @@ else path = wScript.Arguments(0) args = wScript.Arguments(1) error = appRef.DoJavaScriptFile(path, Split(args, ",")) - if Not error = "true" Then + if Not error = "true" and Not error = "[ActionDescriptor]" and Not error = "undefined" Then Err.raise 1, "dojs.vbs", error end if end if \ No newline at end of file diff --git a/scripts/fmtText.jsx b/scripts/fmtText.jsx new file mode 100644 index 0000000..9b88947 --- /dev/null +++ b/scripts/fmtText.jsx @@ -0,0 +1,54 @@ +var start = parseInt(arguments[1]); +var end = parseInt(arguments[2]); +var fontName = arguments[3]; +var fontStyle = arguments[4]; +var colorArray = [0, 0, 0]; +if(app.activeDocument.activeLayer.kind == LayerKind.TEXT){ + var activeLayer = app.activeDocument.activeLayer; + var fontSize = activeLayer.textItem.size; + if(activeLayer.kind == LayerKind.TEXT){ + if((activeLayer.textItem.contents != "")&&(start >= 0)&&(end <= activeLayer.textItem.contents.length)){ + var idsetd = app.charIDToTypeID( "setd" ); + var action = new ActionDescriptor(); + var idnull = app.charIDToTypeID( "null" ); + var reference = new ActionReference(); + var idTxLr = app.charIDToTypeID( "TxLr" ); + var idOrdn = app.charIDToTypeID( "Ordn" ); + var idTrgt = app.charIDToTypeID( "Trgt" ); + reference.putEnumerated( idTxLr, idOrdn, idTrgt ); + action.putReference( idnull, reference ); + var idT = app.charIDToTypeID( "T " ); + var textAction = new ActionDescriptor(); + var idTxtt = app.charIDToTypeID( "Txtt" ); + var actionList = new ActionList(); + var textRange = new ActionDescriptor(); + var idFrom = app.charIDToTypeID( "From" ); + textRange.putInteger( idFrom, start ); + textRange.putInteger( idT, end ); + var idTxtS = app.charIDToTypeID( "TxtS" ); + var formatting = new ActionDescriptor(); + var idFntN = app.charIDToTypeID( "FntN" ); + formatting.putString( idFntN, fontName ); + var idFntS = app.charIDToTypeID( "FntS" ); + formatting.putString( idFntS, fontStyle ); + var idSz = app.charIDToTypeID( "Sz " ); + var idPnt = app.charIDToTypeID( "#Pnt" ); + formatting.putUnitDouble( idSz, idPnt, fontSize ); + var idClr = app.charIDToTypeID( "Clr " ); + var colorAction = new ActionDescriptor(); + var idRd = app.charIDToTypeID( "Rd " ); + colorAction.putDouble( idRd, colorArray[0] ); + var idGrn = app.charIDToTypeID( "Grn " ); + colorAction.putDouble( idGrn, colorArray[1]); + var idBl = app.charIDToTypeID( "Bl " ); + colorAction.putDouble( idBl, colorArray[2] ); + var idRGBC = app.charIDToTypeID( "RGBC" ); + formatting.putObject( idClr, idRGBC, colorAction ); + textRange.putObject( idTxtS, idTxtS, formatting ); + actionList.putObject( idTxtt, textRange ); + textAction.putList( idTxtt, actionList ); + action.putObject( idT, idTxLr, textAction ); + app.executeAction( idsetd, action, DialogModes.NO ); + } + } +} diff --git a/scripts/lib.js b/scripts/lib.js index 70a5983..99da88e 100644 --- a/scripts/lib.js +++ b/scripts/lib.js @@ -17,4 +17,68 @@ function bounds(lyr) { return ('"Bounds": [[' + lyr.bounds[0] + ',' + lyr.bounds[1] + '],[' + lyr.bounds[2] + ',' + lyr.bounds[3] + ']]').replace(/ px/g, ""); +} + +/** +* The setFormatting function sets the font, font style, point size, and RGB color of specified +* characters in a Photoshop text layer. +* +* @param start (int) the index of the insertion point *before* the character you want., +* @param end (int) the index of the insertion point following the character. +* @param fontName is a string for the font name. +* @param fontStyle is a string for the font style. +* @param fontSize (Number) the point size of the text. +* @param colorArray (Array) is the RGB color to be applied to the text. +*/ +function setFormatting(start, end, fontName, fontStyle, fontSize, colorArray) { + if(app.activeDocument.activeLayer.kind == LayerKind.TEXT){ + var activeLayer = app.activeDocument.activeLayer; + fontSize = activeLayer.textItem.size; + colorArray = [0, 0, 0]; + if(activeLayer.kind == LayerKind.TEXT){ + if((activeLayer.textItem.contents != "")&&(start >= 0)&&(end <= activeLayer.textItem.contents.length)){ + var idsetd = app.charIDToTypeID( "setd" ); + var action = new ActionDescriptor(); + var idnull = app.charIDToTypeID( "null" ); + var reference = new ActionReference(); + var idTxLr = app.charIDToTypeID( "TxLr" ); + var idOrdn = app.charIDToTypeID( "Ordn" ); + var idTrgt = app.charIDToTypeID( "Trgt" ); + reference.putEnumerated( idTxLr, idOrdn, idTrgt ); + action.putReference( idnull, reference ); + var idT = app.charIDToTypeID( "T " ); + var textAction = new ActionDescriptor(); + var idTxtt = app.charIDToTypeID( "Txtt" ); + var actionList = new ActionList(); + var textRange = new ActionDescriptor(); + var idFrom = app.charIDToTypeID( "From" ); + textRange.putInteger( idFrom, start ); + textRange.putInteger( idT, end ); + var idTxtS = app.charIDToTypeID( "TxtS" ); + var formatting = new ActionDescriptor(); + var idFntN = app.charIDToTypeID( "FntN" ); + formatting.putString( idFntN, fontName ); + var idFntS = app.charIDToTypeID( "FntS" ); + formatting.putString( idFntS, fontStyle ); + var idSz = app.charIDToTypeID( "Sz " ); + var idPnt = app.charIDToTypeID( "#Pnt" ); + formatting.putUnitDouble( idSz, idPnt, fontSize ); + var idClr = app.charIDToTypeID( "Clr " ); + var colorAction = new ActionDescriptor(); + var idRd = app.charIDToTypeID( "Rd " ); + colorAction.putDouble( idRd, colorArray[0] ); + var idGrn = app.charIDToTypeID( "Grn " ); + colorAction.putDouble( idGrn, colorArray[1]); + var idBl = app.charIDToTypeID( "Bl " ); + colorAction.putDouble( idBl, colorArray[2] ); + var idRGBC = app.charIDToTypeID( "RGBC" ); + formatting.putObject( idClr, idRGBC, colorAction ); + textRange.putObject( idTxtS, idTxtS, formatting ); + actionList.putObject( idTxtt, textRange ); + textAction.putList( idTxtt, actionList ); + action.putObject( idT, idTxLr, textAction ); + app.executeAction( idsetd, action, DialogModes.NO ); + } + } + } } \ No newline at end of file diff --git a/scripts/test.jsx b/scripts/test.jsx index bc0c818..7e2b9c5 100644 --- a/scripts/test.jsx +++ b/scripts/test.jsx @@ -1,3 +1,4 @@ +#include lib.js var saveFile = File(arguments[0]); if(saveFile.exists) saveFile.remove(); @@ -7,4 +8,5 @@ saveFile.open("e", "TEXT", "????"); for (var i = 0; i < arguments.length; i++) { saveFile.writeln(arguments[i]) } +setFormatting(0,6, "Arial", "Bold"); saveFile.close(); \ No newline at end of file diff --git a/structs.go b/structs.go index fa18621..c8e36c2 100644 --- a/structs.go +++ b/structs.go @@ -1,7 +1,9 @@ +// TODO: Count skipped steps. package ps import ( "encoding/json" + "errors" "fmt" "io/ioutil" "log" @@ -267,7 +269,7 @@ func (a *ArtLayer) SetActive() ([]byte, error) { func (a *ArtLayer) SetColor(c Color) { if a.Color.RGB() == c.RGB() { if Mode == 2 || (Mode == 0 && a.current) { - log.Println("Skipping color: already set.") + // log.Println("Skipping color: already set.") return } } @@ -299,10 +301,15 @@ func (a *ArtLayer) SetColor(c Color) { } func (a *ArtLayer) SetStroke(stk Stroke, fill Color) { + if stk.Size == 0 { + a.Stroke = &stk + a.SetColor(fill) + return + } if stk.Size == a.Stroke.Size && stk.Color.RGB() == a.Stroke.Color.RGB() { if a.Color.RGB() == fill.RGB() { if Mode == 2 || (Mode == 0 && a.current) { - log.Println("Skipping stroke: already set.") + // log.Println("Skipping stroke: already set.") return } } @@ -314,8 +321,6 @@ func (a *ArtLayer) SetStroke(stk Stroke, fill Color) { if err != nil { log.Panic(err) } - log.Printf(" layer %s stroke was %.2fpt %v and color to %v\n", a.name, a.Stroke.Size, - a.Stroke.Color.RGB(), a.Color.RGB()) a.Stroke = &stk a.Color = fill stkCol := stk.Color.RGB() @@ -340,6 +345,17 @@ func (a *ArtLayer) Path() string { return fmt.Sprintf("%s%s", a.parent.Path(), a.name) } +func (a *ArtLayer) Format(start, end int, font, style string) { + if !a.Visible() { + return + } + _, err := DoJs("fmtText.jsx", fmt.Sprint(start), fmt.Sprint(end), + font, style) + if err != nil { + log.Panic(err) + } +} + // Layer returns an ArtLayer from the active document given a specified // path string. func Layer(path string) (ArtLayer, error) { @@ -484,6 +500,7 @@ func (l *LayerSet) ArtLayers() []*ArtLayer { // ArtLayer returns the first top level ArtLayer matching // the given name. +// TODO: Does funky things when passed invalid layername. func (l *LayerSet) ArtLayer(name string) *ArtLayer { for _, lyr := range l.artLayers { if lyr.name == name { @@ -491,16 +508,24 @@ func (l *LayerSet) ArtLayer(name string) *ArtLayer { err := lyr.Refresh() if err != nil { l.Refresh() + err := lyr.Refresh() + if err != nil { + log.Panic(err) + } } } return lyr } } - l.Refresh() - for _, lyr := range l.artLayers { - fmt.Println(lyr) - } + // l.Refresh() + // for _, lyr := range l.artLayers { + // fmt.Println(lyr) + // } lyr := l.ArtLayer(name) + fmt.Println(lyr) + if lyr == nil { + log.Panic(errors.New("Layer not found!")) + } return lyr } @@ -577,7 +602,7 @@ func (l *LayerSet) Refresh() { byt, err := DoJs("getLayerSet.jsx", JSLayer(l.Path())) err = json.Unmarshal(byt, &tmp) if err != nil { - log.Println(string(byt)) + log.Println("Error in LayerSet.Refresh()", string(byt)) log.Panic(err) } tmp.SetParent(l.Parent()) From c54b196f6a2ec5fa073872377ce2c7a2487326d7 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 18 Apr 2018 23:58:38 -0400 Subject: [PATCH 21/22] Various updates * Layersets now have bounds. * TextItem is now writeable * Added flush function to writing, for faster debugging. --- ps.go | 8 +++-- scripts/getLayerSet.jsx | 20 +++++++++--- scripts/lib.js | 22 +++++++++++-- scripts/moveLayer.jsx | 7 +++++ scripts/test.jsx | 19 ++++++----- structs.go | 70 +++++++++++++++++++++++++++++++++++++++-- 6 files changed, 125 insertions(+), 21 deletions(-) diff --git a/ps.go b/ps.go index 2450b1d..93d7090 100644 --- a/ps.go +++ b/ps.go @@ -73,7 +73,7 @@ func SaveAs(path string) error { func DoJs(path string, args ...string) (out []byte, err error) { // Temp file for js to output to. outpath := filepath.Join(os.Getenv("TEMP"), "js_out.txt") - defer os.Remove(outpath) + // defer os.Remove(outpath) if !strings.HasSuffix(path, ".jsx") { path += ".jsx" } @@ -160,11 +160,15 @@ func ApplyDataset(name string) ([]byte, error) { // JSLayer "compiles" Javascript code to get an ArtLayer with the given path. // The output always ends with a semicolon, so if you want to access a specific // property of the layer, you'll have to trim the output before concatenating -func JSLayer(path string) string { +func JSLayer(path string, art ...bool) string { path = strings.TrimLeft(path, "/") pth := strings.Split(path, "/") js := "app.activeDocument" last := len(pth) - 1 + if len(art) > 0 { + pth = pth[:len(pth)-1] + last-- + } if last > 0 { for i := 0; i < last; i++ { js += fmt.Sprintf(".layerSets.getByName('%s')", pth[i]) diff --git a/scripts/getLayerSet.jsx b/scripts/getLayerSet.jsx index 313618a..0f43574 100644 --- a/scripts/getLayerSet.jsx +++ b/scripts/getLayerSet.jsx @@ -1,8 +1,8 @@ #include lib.js - var stdout = newFile(arguments[0]); var set = eval(arguments[1]); stdout.writeln('{"Name": "'+set.name+'", "ArtLayers":['); +stdout.flush(); for (var i = 0; i < set.artLayers.length; i++) { var lyr = set.artLayers[i]; stdout.write(('{"Name":"' + lyr.name + '", "Bounds": [[' + lyr.bounds[0] + ',' + @@ -15,13 +15,25 @@ for (var i = 0; i < set.artLayers.length; i++) { stdout.write("}") if (i != set.artLayers.length - 1) stdout.writeln(","); + stdout.flush(); } -stdout.write('], "LayerSets": [') +stdout.writeln("]"); +stdout.write(', "LayerSets": [') for (var i = 0; i < set.layerSets.length; i++) { var s = set.layerSets[i]; - stdout.write('{"Name": "'+ s.name +'", "Visible": '+s.visible+'}'); + stdout.write('{"Name":"' + s.name + '", "Visible": ' + s.visible + '}'); if (i < set.layerSets.length - 1) stdout.writeln(","); + stdout.flush() } -stdout.write("]}") +stdout.writeln(']') +// app.activeDocument.activeLayer=set; +// set.merge(); +// set=eval(arguments[2]); +stdout.write(', "Bounds": [[],[]]'); +// stdout.write((', "Bounds": [[' + set.bounds[0] + ',' + + // set.bounds[1] + '],[' + set.bounds[2] + ',' + + // set.bounds[3] + ']]').replace(/ px/g, "")); +stdout.write("}"); +// Undo(); stdout.close(); \ No newline at end of file diff --git a/scripts/lib.js b/scripts/lib.js index 99da88e..a409c0b 100644 --- a/scripts/lib.js +++ b/scripts/lib.js @@ -1,13 +1,21 @@ // Opens and returns a file, overwriting new data. function newFile(path) { var f = File(path) - if(f.exists) - f.remove() f.encoding = "UTF8" - f.open("e", "TEXT", "????") + f.open("w") return f } +File.prototype.flush = function() { + this.close() + this.open("a") +}; +function flush(file) { + file.close() + file.open("a") +} + + // Prints an error message. function err(e) { return 'ERROR: ' + e.message + ' at ' + e.fileName + ':' + e.line; @@ -19,6 +27,14 @@ function bounds(lyr) { lyr.bounds[3] + ']]').replace(/ px/g, ""); } +function Undo() { + var desc = new ActionDescriptor(); + var ref = new ActionReference(); + ref.putEnumerated( charIDToTypeID( "HstS" ), charIDToTypeID( "Ordn" ), charIDToTypeID( "Prvs" )); + desc.putReference(charIDToTypeID( "null" ), ref); + executeAction( charIDToTypeID( "slct" ), desc, DialogModes.NO ); +} + /** * The setFormatting function sets the font, font style, point size, and RGB color of specified * characters in a Photoshop text layer. diff --git a/scripts/moveLayer.jsx b/scripts/moveLayer.jsx index c3a4cf9..450a514 100644 --- a/scripts/moveLayer.jsx +++ b/scripts/moveLayer.jsx @@ -2,5 +2,12 @@ var stdout = newFile(arguments[0]); var lyr = eval(arguments[1]); lyr.translate((Number)(arguments[2]), (Number)(arguments[3])); +if (lyr.typename == 'LayerSet') { + alert(lyr.name + "\n" + lyr.typename) + alert(lyr) + // lyr.merge() + // lyr=eval(arguments[4]) + // Undo(); +} stdout.writeln('{' + bounds(lyr) + '}') stdout.close(); \ No newline at end of file diff --git a/scripts/test.jsx b/scripts/test.jsx index 7e2b9c5..bc18da5 100644 --- a/scripts/test.jsx +++ b/scripts/test.jsx @@ -1,12 +1,11 @@ #include lib.js -var saveFile = File(arguments[0]); -if(saveFile.exists) - saveFile.remove(); -saveFile.encoding = "UTF8"; -saveFile.open("e", "TEXT", "????"); -for (var i = 0; i < arguments.length; i++) { - saveFile.writeln(arguments[i]) -} -setFormatting(0,6, "Arial", "Bold"); -saveFile.close(); \ No newline at end of file +// var saveFile = File(arguments[0]); +var arg = 'app.activeDocument.layerSets.getByName("Indicators").layerSets.getByName("Deck")'; +alert(arg.replace(/[^(?:layerSets)]*(layerSets)/, "artLayers")) +// var doc=app.activeDocument +// doc.layerSets.getByName("ResolveGem").merge(); +// alert(doc.artLayers.getByName("ResolveGem").bounds); +// doc.activeHistoryState=doc.historyStates[doc.historyStates.length-2] +// setFormatting(0,6, "Arial", "Bold"); +// saveFile.close(); \ No newline at end of file diff --git a/structs.go b/structs.go index c8e36c2..cf6dc63 100644 --- a/structs.go +++ b/structs.go @@ -168,6 +168,7 @@ func (d *Document) Dump() { } // ArtLayer reflects certain values from an Art Layer in a Photoshop document. +// TODO: Make TextLayer a subclass of ArtLayer type ArtLayer struct { name string // The layer's name. Text *string // The contents of a text layer. @@ -197,6 +198,16 @@ type ArtLayerJSON struct { Text *string } +func (a *ArtLayer) SetText(txt string) { + lyr := strings.TrimRight(JSLayer(a.Path()), ";") + js := fmt.Sprintf("%s.textItem.contents='%s';", lyr, txt) + _, err := DoJs("compilejs.jsx", js) + if err != nil { + a.Text = &txt + } + a.Refresh() +} + // MarshalJSON fulfills the json.Marshaler interface, allowing the ArtLayer to be // saved to disk in JSON format. func (a *ArtLayer) MarshalJSON() ([]byte, error) { @@ -444,6 +455,7 @@ func (a *ArtLayer) Refresh() error { type LayerSet struct { name string + bounds [2][2]int parent Group current bool // Whether we've checked this layer since we loaded from disk. visible bool @@ -453,6 +465,7 @@ type LayerSet struct { type LayerSetJSON struct { Name string + Bounds [2][2]int ArtLayers []*ArtLayer LayerSets []*LayerSet } @@ -460,6 +473,7 @@ type LayerSetJSON struct { func (l *LayerSet) MarshalJSON() ([]byte, error) { return json.Marshal(&LayerSetJSON{ Name: l.name, + Bounds: l.bounds, ArtLayers: l.artLayers, LayerSets: l.layerSets, }) @@ -471,6 +485,7 @@ func (l *LayerSet) UnmarshalJSON(b []byte) error { return err } l.name = tmp.Name + l.bounds = tmp.Bounds l.artLayers = tmp.ArtLayers for _, lyr := range l.artLayers { lyr.SetParent(l) @@ -544,6 +559,11 @@ func (l *LayerSet) LayerSet(name string) *LayerSet { return nil } +// Bounds returns the furthest corners of the LayerSet. +func (l *LayerSet) Bounds() [2][2]int { + return l.bounds +} + func (l *LayerSet) SetParent(c Group) { l.parent = c } @@ -562,9 +582,13 @@ func (l *LayerSet) Path() string { func NewLayerSet(path string, g Group) (*LayerSet, error) { path = strings.Replace(path, "//", "/", -1) byt, err := DoJs("getLayerSet.jsx", JSLayer(path)) + if err != nil { + log.Panic(err) + } var out *LayerSet err = json.Unmarshal(byt, &out) if err != nil { + log.Println(JSLayer(path)) log.Println(string(byt)) log.Panic(err) } @@ -597,12 +621,53 @@ func (l *LayerSet) SetVisible(b bool) { DoJs("compilejs.jsx", js) } +// SetPos snaps the given layerset boundry to the given point. +// Valid options for bound are: "TL", "TR", "BL", "BR" +// +// TODO: Test TR and BR +func (l *LayerSet) SetPos(x, y int, bound string) { + // if !l.visible || (x == 0 && y == 0) { + // return + // } + var lyrX, lyrY int + switch bound[:1] { + case "B": + lyrY = l.bounds[1][1] + case "T": + fallthrough + default: + lyrY = l.bounds[0][1] + } + switch bound[1:] { + case "R": + lyrX = l.bounds[1][0] + case "L": + fallthrough + default: + lyrX = l.bounds[0][0] + } + fmt.Println(JSLayer(l.Path()), fmt.Sprint(x-lyrX), fmt.Sprint(y-lyrY)) + byt, err := DoJs("moveLayer.jsx", JSLayer(l.Path()), fmt.Sprint(x-lyrX), + fmt.Sprint(y-lyrY), JSLayer(l.Path(), true)) + if err != nil { + fmt.Println("byte:", string(byt)) + panic(err) + } + var lyr LayerSet + err = json.Unmarshal(byt, &lyr) + if err != nil { + fmt.Println("byte:", string(byt)) + log.Panic(err) + } + l.bounds = lyr.bounds +} + func (l *LayerSet) Refresh() { var tmp *LayerSet - byt, err := DoJs("getLayerSet.jsx", JSLayer(l.Path())) + byt, err := DoJs("getLayerSet.jsx", JSLayer(l.Path()), JSLayer(l.Path(), true)) err = json.Unmarshal(byt, &tmp) if err != nil { - log.Println("Error in LayerSet.Refresh()", string(byt)) + log.Println("Error in LayerSet.Refresh() \"", string(byt), "\"", "for", l.Path()) log.Panic(err) } tmp.SetParent(l.Parent()) @@ -617,6 +682,7 @@ func (l *LayerSet) Refresh() { set.Refresh() } l.name = tmp.name + l.bounds = tmp.bounds l.visible = tmp.visible l.current = true } From cb62b35caa925aa38e3943d2298a7bed97ebe8b5 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 19 Apr 2018 14:52:32 -0400 Subject: [PATCH 22/22] Layersets now can be moved correctly --- scripts/getActiveDoc.jsx | 4 +-- scripts/getLayerSet.jsx | 2 +- scripts/layerSetBounds.jsx | 11 ++++++++ scripts/moveLayer.jsx | 8 +++--- scripts/test.jsx | 6 +++-- structs.go | 55 ++++++++++++++++++++++++++++++-------- 6 files changed, 65 insertions(+), 21 deletions(-) create mode 100644 scripts/layerSetBounds.jsx diff --git a/scripts/getActiveDoc.jsx b/scripts/getActiveDoc.jsx index fbcfb7d..c8d2c06 100644 --- a/scripts/getActiveDoc.jsx +++ b/scripts/getActiveDoc.jsx @@ -10,7 +10,7 @@ function layers(lyrs) { var lyr = lyrs[i]; stdout.write(('{"Name":"' + lyr.name + '", "Bounds": [[' + lyr.bounds[0] + ',' + lyr.bounds[1] + '],[' + lyr.bounds[2] + ',' + - lyr.bounds[3] + ']], "Visible": ' + lyr.visible+',"Text":').replace(/ px/g, "")); + lyr.bounds[3] + ']], "Visible": ' + lyr.visible+', "Text":').replace(/ px/g, "")); if (lyr.kind == LayerKind.TEXT) stdout.write('"'+lyr.textItem.contents+'"'); else @@ -29,7 +29,7 @@ function lyrSets(sets, nm) { for (var i = 0; i < sets.length; i++) { var set = sets[i]; var name = nm + set.name + "/"; - stdout.write('{"Name": "' + set.name + '"}'); + stdout.write('{"Name": "' + set.name + '", "Visible":'+ set.visible +'}'); if (i+1 != sets.length) stdout.write(','); } diff --git a/scripts/getLayerSet.jsx b/scripts/getLayerSet.jsx index 0f43574..24117de 100644 --- a/scripts/getLayerSet.jsx +++ b/scripts/getLayerSet.jsx @@ -1,7 +1,7 @@ #include lib.js var stdout = newFile(arguments[0]); var set = eval(arguments[1]); -stdout.writeln('{"Name": "'+set.name+'", "ArtLayers":['); +stdout.writeln('{"Name": "'+set.name+'", "Visible": '+ set.visible +', "ArtLayers":['); stdout.flush(); for (var i = 0; i < set.artLayers.length; i++) { var lyr = set.artLayers[i]; diff --git a/scripts/layerSetBounds.jsx b/scripts/layerSetBounds.jsx new file mode 100644 index 0000000..b1def30 --- /dev/null +++ b/scripts/layerSetBounds.jsx @@ -0,0 +1,11 @@ +#include lib.js +var stdout = newFile(arguments[0]); +var set = eval(arguments[1]); +app.activeDocument.activeLayer=set; +set.merge(); +set=eval(arguments[2]); +stdout.write(('[[' + set.bounds[0] + ',' + + set.bounds[1] + '],[' + set.bounds[2] + ',' + + set.bounds[3] + ']]').replace(/ px/g, "")); +Undo(); +stdout.close(); \ No newline at end of file diff --git a/scripts/moveLayer.jsx b/scripts/moveLayer.jsx index 450a514..12187eb 100644 --- a/scripts/moveLayer.jsx +++ b/scripts/moveLayer.jsx @@ -3,11 +3,9 @@ var stdout = newFile(arguments[0]); var lyr = eval(arguments[1]); lyr.translate((Number)(arguments[2]), (Number)(arguments[3])); if (lyr.typename == 'LayerSet') { - alert(lyr.name + "\n" + lyr.typename) - alert(lyr) - // lyr.merge() - // lyr=eval(arguments[4]) - // Undo(); + lyr.merge() + lyr=eval(arguments[4]) + Undo(); } stdout.writeln('{' + bounds(lyr) + '}') stdout.close(); \ No newline at end of file diff --git a/scripts/test.jsx b/scripts/test.jsx index bc18da5..7f6b4e1 100644 --- a/scripts/test.jsx +++ b/scripts/test.jsx @@ -1,8 +1,10 @@ #include lib.js // var saveFile = File(arguments[0]); -var arg = 'app.activeDocument.layerSets.getByName("Indicators").layerSets.getByName("Deck")'; -alert(arg.replace(/[^(?:layerSets)]*(layerSets)/, "artLayers")) +var arg = 'app.activeDocument.layerSets.getByName("ResolveGem");'; +var set = eval(arg); +set.visible=false; +alert(set.visible) // var doc=app.activeDocument // doc.layerSets.getByName("ResolveGem").merge(); // alert(doc.artLayers.getByName("ResolveGem").bounds); diff --git a/structs.go b/structs.go index cf6dc63..f29edfb 100644 --- a/structs.go +++ b/structs.go @@ -91,6 +91,9 @@ func (d *Document) LayerSets() []*LayerSet { func (d *Document) LayerSet(name string) *LayerSet { for _, set := range d.layerSets { if set.name == name { + if Mode != Fast && !set.current { + set.Refresh() + } return set } } @@ -124,8 +127,8 @@ func ActiveDocument() (*Document, error) { if err != nil { return nil, err } - d.name = string(byt) - if Mode != 1 { + d.name = strings.TrimRight(string(byt), "\r\n") + if Mode != Safe { byt, err = ioutil.ReadFile(d.Filename()) if err == nil { log.Println("Previous version found, loading") @@ -137,7 +140,15 @@ func ActiveDocument() (*Document, error) { } log.Println("Loading manually (This could take awhile)") byt, err = DoJs("getActiveDoc.jsx") + if err != nil { + log.Panic(err) + } err = json.Unmarshal(byt, &d) + if err != nil { + d.Dump() + fmt.Println(string(byt)) + log.Panic(err) + } for _, lyr := range d.artLayers { lyr.SetParent(d) } @@ -317,6 +328,9 @@ func (a *ArtLayer) SetStroke(stk Stroke, fill Color) { a.SetColor(fill) return } + if fill == nil { + fill = a.Color + } if stk.Size == a.Stroke.Size && stk.Color.RGB() == a.Stroke.Color.RGB() { if a.Color.RGB() == fill.RGB() { if Mode == 2 || (Mode == 0 && a.current) { @@ -466,6 +480,7 @@ type LayerSet struct { type LayerSetJSON struct { Name string Bounds [2][2]int + Visible bool ArtLayers []*ArtLayer LayerSets []*LayerSet } @@ -474,6 +489,7 @@ func (l *LayerSet) MarshalJSON() ([]byte, error) { return json.Marshal(&LayerSetJSON{ Name: l.name, Bounds: l.bounds, + Visible: l.visible, ArtLayers: l.artLayers, LayerSets: l.layerSets, }) @@ -486,6 +502,7 @@ func (l *LayerSet) UnmarshalJSON(b []byte) error { } l.name = tmp.Name l.bounds = tmp.Bounds + l.visible = tmp.Visible l.artLayers = tmp.ArtLayers for _, lyr := range l.artLayers { lyr.SetParent(l) @@ -608,27 +625,44 @@ func NewLayerSet(path string, g Group) (*LayerSet, error) { out.layerSets[i] = s s.SetParent(out) } + out.current = true return out, err } +func (l *LayerSet) Visible() bool { + return l.visible +} + // SetVisible makes the LayerSet visible. func (l *LayerSet) SetVisible(b bool) { if l.visible == b { return } - l.visible = b - js := fmt.Sprintf("%s%v", JSLayer(strings.TrimRight(l.Path(), ";")), b) + js := fmt.Sprintf("%s.visible=%v;", strings.TrimRight( + JSLayer(l.Path()), ";"), b) DoJs("compilejs.jsx", js) + l.visible = b } // SetPos snaps the given layerset boundry to the given point. // Valid options for bound are: "TL", "TR", "BL", "BR" -// -// TODO: Test TR and BR func (l *LayerSet) SetPos(x, y int, bound string) { - // if !l.visible || (x == 0 && y == 0) { - // return - // } + if !l.visible || (x == 0 && y == 0) { + return + } + byt, err := DoJs("LayerSetBounds.jsx", JSLayer(l.Path()), + JSLayer(l.Path(), true)) + if err != nil { + log.Println(string(byt)) + log.Panic(err) + } + var bnds *[2][2]int + err = json.Unmarshal(byt, &bnds) + if err != nil { + fmt.Println(string(byt)) + log.Panic(err) + } + l.bounds = *bnds var lyrX, lyrY int switch bound[:1] { case "B": @@ -646,8 +680,7 @@ func (l *LayerSet) SetPos(x, y int, bound string) { default: lyrX = l.bounds[0][0] } - fmt.Println(JSLayer(l.Path()), fmt.Sprint(x-lyrX), fmt.Sprint(y-lyrY)) - byt, err := DoJs("moveLayer.jsx", JSLayer(l.Path()), fmt.Sprint(x-lyrX), + byt, err = DoJs("moveLayer.jsx", JSLayer(l.Path()), fmt.Sprint(x-lyrX), fmt.Sprint(y-lyrY), JSLayer(l.Path(), true)) if err != nil { fmt.Println("byte:", string(byt))